summaryrefslogtreecommitdiff
path: root/fsck.c
diff options
context:
space:
mode:
Diffstat (limited to 'fsck.c')
-rw-r--r--fsck.c306
1 files changed, 138 insertions, 168 deletions
diff --git a/fsck.c b/fsck.c
index 2b1e348005..3756f52459 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1,3 +1,5 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "git-compat-util.h"
#include "date.h"
#include "dir.h"
@@ -16,14 +18,14 @@
#include "refs.h"
#include "url.h"
#include "utf8.h"
-#include "decorate.h"
#include "oidset.h"
#include "packfile.h"
#include "submodule-config.h"
#include "config.h"
-#include "credential.h"
#include "help.h"
+static ssize_t max_tree_entry_len = 4096;
+
#define STR(x) #x
#define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type },
static struct {
@@ -154,15 +156,29 @@ void fsck_set_msg_type(struct fsck_options *options,
const char *msg_id_str, const char *msg_type_str)
{
int msg_id = parse_msg_id(msg_id_str);
- enum fsck_msg_type msg_type = parse_msg_type(msg_type_str);
+ char *to_free = NULL;
+ enum fsck_msg_type msg_type;
if (msg_id < 0)
die("Unhandled message id: %s", msg_id_str);
+ if (msg_id == FSCK_MSG_LARGE_PATHNAME) {
+ const char *colon = strchr(msg_type_str, ':');
+ if (colon) {
+ msg_type_str = to_free =
+ xmemdupz(msg_type_str, colon - msg_type_str);
+ colon++;
+ if (!git_parse_ssize_t(colon, &max_tree_entry_len))
+ die("unable to parse max tree entry len: %s", colon);
+ }
+ }
+ msg_type = parse_msg_type(msg_type_str);
+
if (msg_type != FSCK_ERROR && msg_id_info[msg_id].msg_type == FSCK_FATAL)
die("Cannot demote %s to %s", msg_id_str, msg_type_str);
fsck_set_msg_type_from_ids(options, msg_id, msg_type);
+ free(to_free);
}
void fsck_set_msg_types(struct fsck_options *options, const char *values)
@@ -189,7 +205,8 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
if (!strcmp(buf, "skiplist")) {
if (equal == len)
die("skiplist requires a path");
- oidset_parse_file(&options->skiplist, buf + equal + 1);
+ oidset_parse_file(&options->skip_oids, buf + equal + 1,
+ the_repository->hash_algo);
buf += len + 1;
continue;
}
@@ -206,15 +223,18 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
static int object_on_skiplist(struct fsck_options *opts,
const struct object_id *oid)
{
- return opts && oid && oidset_contains(&opts->skiplist, oid);
+ return opts && oid && oidset_contains(&opts->skip_oids, oid);
}
-__attribute__((format (printf, 5, 6)))
-static int report(struct fsck_options *options,
- const struct object_id *oid, enum object_type object_type,
- enum fsck_msg_id msg_id, const char *fmt, ...)
+/*
+ * Provide the common functionality for either fscking refs or objects.
+ * It will get the current msg error type and call the error_func callback
+ * which is registered in the "fsck_options" struct.
+ */
+static int fsck_vreport(struct fsck_options *options,
+ void *fsck_report,
+ enum fsck_msg_id msg_id, const char *fmt, va_list ap)
{
- va_list ap;
struct strbuf sb = STRBUF_INIT;
enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options);
int result;
@@ -222,9 +242,6 @@ static int report(struct fsck_options *options,
if (msg_type == FSCK_IGNORE)
return 0;
- if (object_on_skiplist(options, oid))
- return 0;
-
if (msg_type == FSCK_FATAL)
msg_type = FSCK_ERROR;
else if (msg_type == FSCK_INFO)
@@ -233,16 +250,49 @@ static int report(struct fsck_options *options,
prepare_msg_ids();
strbuf_addf(&sb, "%s: ", msg_id_info[msg_id].camelcased);
- va_start(ap, fmt);
strbuf_vaddf(&sb, fmt, ap);
- result = options->error_func(options, oid, object_type,
+ result = options->error_func(options, fsck_report,
msg_type, msg_id, sb.buf);
strbuf_release(&sb);
+
+ return result;
+}
+
+__attribute__((format (printf, 5, 6)))
+static int report(struct fsck_options *options,
+ const struct object_id *oid, enum object_type object_type,
+ enum fsck_msg_id msg_id, const char *fmt, ...)
+{
+ va_list ap;
+ struct fsck_object_report report = {
+ .oid = oid,
+ .object_type = object_type
+ };
+ int result;
+
+ if (object_on_skiplist(options, oid))
+ return 0;
+
+ va_start(ap, fmt);
+ result = fsck_vreport(options, &report, msg_id, fmt, ap);
va_end(ap);
return result;
}
+int fsck_report_ref(struct fsck_options *options,
+ struct fsck_ref_report *report,
+ enum fsck_msg_id msg_id,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int result;
+ va_start(ap, fmt);
+ result = fsck_vreport(options, report, msg_id, fmt, ap);
+ va_end(ap);
+ return result;
+}
+
void fsck_enable_object_names(struct fsck_options *options)
{
if (!options->object_names)
@@ -313,7 +363,8 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
return -1;
name = fsck_get_object_name(options, &tree->object.oid);
- if (init_tree_desc_gently(&desc, tree->buffer, tree->size, 0))
+ if (init_tree_desc_gently(&desc, &tree->object.oid,
+ tree->buffer, tree->size, 0))
return -1;
while (tree_entry_gently(&desc, &entry)) {
struct object *obj;
@@ -578,12 +629,14 @@ static int fsck_tree(const struct object_id *tree_oid,
int has_bad_modes = 0;
int has_dup_entries = 0;
int not_properly_sorted = 0;
+ int has_large_name = 0;
struct tree_desc desc;
unsigned o_mode;
const char *o_name;
struct name_stack df_dup_candidates = { NULL };
- if (init_tree_desc_gently(&desc, buffer, size, TREE_DESC_RAW_MODES)) {
+ if (init_tree_desc_gently(&desc, tree_oid, buffer, size,
+ TREE_DESC_RAW_MODES)) {
retval += report(options, tree_oid, OBJ_TREE,
FSCK_MSG_BAD_TREE,
"cannot be parsed as a tree");
@@ -607,6 +660,7 @@ static int fsck_tree(const struct object_id *tree_oid,
has_dotdot |= !strcmp(name, "..");
has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name);
has_zero_pad |= *(char *)desc.buffer == '0';
+ has_large_name |= tree_entry_len(&desc.entry) > max_tree_entry_len;
if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
if (!S_ISLNK(mode))
@@ -749,6 +803,10 @@ static int fsck_tree(const struct object_id *tree_oid,
retval += report(options, tree_oid, OBJ_TREE,
FSCK_MSG_TREE_NOT_SORTED,
"not properly sorted");
+ if (has_large_name)
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_LARGE_PATHNAME,
+ "contains excessively large pathname");
return retval;
}
@@ -1026,138 +1084,6 @@ done:
return ret;
}
-static int starts_with_dot_slash(const char *const path)
-{
- return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH |
- PATH_MATCH_XPLATFORM);
-}
-
-static int starts_with_dot_dot_slash(const char *const path)
-{
- return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH |
- PATH_MATCH_XPLATFORM);
-}
-
-static int submodule_url_is_relative(const char *url)
-{
- return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
-}
-
-/*
- * Count directory components that a relative submodule URL should chop
- * from the remote_url it is to be resolved against.
- *
- * In other words, this counts "../" components at the start of a
- * submodule URL.
- *
- * Returns the number of directory components to chop and writes a
- * pointer to the next character of url after all leading "./" and
- * "../" components to out.
- */
-static int count_leading_dotdots(const char *url, const char **out)
-{
- int result = 0;
- while (1) {
- if (starts_with_dot_dot_slash(url)) {
- result++;
- url += strlen("../");
- continue;
- }
- if (starts_with_dot_slash(url)) {
- url += strlen("./");
- continue;
- }
- *out = url;
- return result;
- }
-}
-/*
- * Check whether a transport is implemented by git-remote-curl.
- *
- * If it is, returns 1 and writes the URL that would be passed to
- * git-remote-curl to the "out" parameter.
- *
- * Otherwise, returns 0 and leaves "out" untouched.
- *
- * Examples:
- * http::https://example.com/repo.git -> 1, https://example.com/repo.git
- * https://example.com/repo.git -> 1, https://example.com/repo.git
- * git://example.com/repo.git -> 0
- *
- * This is for use in checking for previously exploitable bugs that
- * required a submodule URL to be passed to git-remote-curl.
- */
-static int url_to_curl_url(const char *url, const char **out)
-{
- /*
- * We don't need to check for case-aliases, "http.exe", and so
- * on because in the default configuration, is_transport_allowed
- * prevents URLs with those schemes from being cloned
- * automatically.
- */
- if (skip_prefix(url, "http::", out) ||
- skip_prefix(url, "https::", out) ||
- skip_prefix(url, "ftp::", out) ||
- skip_prefix(url, "ftps::", out))
- return 1;
- if (starts_with(url, "http://") ||
- starts_with(url, "https://") ||
- starts_with(url, "ftp://") ||
- starts_with(url, "ftps://")) {
- *out = url;
- return 1;
- }
- return 0;
-}
-
-static int check_submodule_url(const char *url)
-{
- const char *curl_url;
-
- if (looks_like_command_line_option(url))
- return -1;
-
- if (submodule_url_is_relative(url) || starts_with(url, "git://")) {
- char *decoded;
- const char *next;
- int has_nl;
-
- /*
- * This could be appended to an http URL and url-decoded;
- * check for malicious characters.
- */
- decoded = url_decode(url);
- has_nl = !!strchr(decoded, '\n');
-
- free(decoded);
- if (has_nl)
- return -1;
-
- /*
- * URLs which escape their root via "../" can overwrite
- * the host field and previous components, resolving to
- * URLs like https::example.com/submodule.git and
- * https:///example.com/submodule.git that were
- * susceptible to CVE-2020-11008.
- */
- if (count_leading_dotdots(url, &next) > 0 &&
- (*next == ':' || *next == '/'))
- return -1;
- }
-
- else if (url_to_curl_url(url, &curl_url)) {
- struct credential c = CREDENTIAL_INIT;
- int ret = 0;
- if (credential_from_url_gently(&c, curl_url, 1) ||
- !*c.host)
- ret = -1;
- credential_clear(&c);
- return ret;
- }
-
- return 0;
-}
-
struct fsck_gitmodules_data {
const struct object_id *oid;
struct fsck_options *options;
@@ -1289,7 +1215,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
}
int fsck_buffer(const struct object_id *oid, enum object_type type,
- void *data, unsigned long size,
+ const void *data, unsigned long size,
struct fsck_options *options)
{
if (type == OBJ_BLOB)
@@ -1307,13 +1233,15 @@ int fsck_buffer(const struct object_id *oid, enum object_type type,
type);
}
-int fsck_error_function(struct fsck_options *o,
- const struct object_id *oid,
- enum object_type object_type UNUSED,
- enum fsck_msg_type msg_type,
- enum fsck_msg_id msg_id UNUSED,
- const char *message)
+int fsck_objects_error_function(struct fsck_options *o,
+ void *fsck_report,
+ enum fsck_msg_type msg_type,
+ enum fsck_msg_id msg_id UNUSED,
+ const char *message)
{
+ struct fsck_object_report *report = fsck_report;
+ const struct object_id *oid = report->oid;
+
if (msg_type == FSCK_WARN) {
warning("object %s: %s", fsck_describe_object(o, oid), message);
return 0;
@@ -1322,6 +1250,32 @@ int fsck_error_function(struct fsck_options *o,
return 1;
}
+int fsck_refs_error_function(struct fsck_options *options UNUSED,
+ void *fsck_report,
+ enum fsck_msg_type msg_type,
+ enum fsck_msg_id msg_id UNUSED,
+ const char *message)
+{
+ struct fsck_ref_report *report = fsck_report;
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
+
+ strbuf_addstr(&sb, report->path);
+
+ if (report->oid)
+ strbuf_addf(&sb, " -> (%s)", oid_to_hex(report->oid));
+ else if (report->referent)
+ strbuf_addf(&sb, " -> (%s)", report->referent);
+
+ if (msg_type == FSCK_WARN)
+ warning("%s: %s", sb.buf, message);
+ else
+ ret = error("%s: %s", sb.buf, message);
+
+ strbuf_release(&sb);
+ return ret;
+}
+
static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done,
enum fsck_msg_id msg_missing, enum fsck_msg_id msg_type,
struct fsck_options *options, const char *blob_type)
@@ -1377,25 +1331,40 @@ int fsck_finish(struct fsck_options *options)
return ret;
}
+void fsck_options_clear(struct fsck_options *options)
+{
+ free(options->msg_type);
+ oidset_clear(&options->skip_oids);
+ oidset_clear(&options->gitmodules_found);
+ oidset_clear(&options->gitmodules_done);
+ oidset_clear(&options->gitattributes_found);
+ oidset_clear(&options->gitattributes_done);
+ kh_clear_oid_map(options->object_names);
+}
+
int git_fsck_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
struct fsck_options *options = cb;
+ const char *msg_id;
+
if (strcmp(var, "fsck.skiplist") == 0) {
- const char *path;
+ char *path;
struct strbuf sb = STRBUF_INIT;
if (git_config_pathname(&path, var, value))
return 1;
strbuf_addf(&sb, "skiplist=%s", path);
- free((char *)path);
+ free(path);
fsck_set_msg_types(options, sb.buf);
strbuf_release(&sb);
return 0;
}
- if (skip_prefix(var, "fsck.", &var)) {
- fsck_set_msg_type(options, var, value);
+ if (skip_prefix(var, "fsck.", &msg_id)) {
+ if (!value)
+ return config_error_nonbool(var);
+ fsck_set_msg_type(options, msg_id, value);
return 0;
}
@@ -1406,16 +1375,17 @@ int git_fsck_config(const char *var, const char *value,
* Custom error callbacks that are used in more than one place.
*/
-int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o,
- const struct object_id *oid,
- enum object_type object_type,
- enum fsck_msg_type msg_type,
- enum fsck_msg_id msg_id,
- const char *message)
+int fsck_objects_error_cb_print_missing_gitmodules(struct fsck_options *o,
+ void *fsck_report,
+ enum fsck_msg_type msg_type,
+ enum fsck_msg_id msg_id,
+ const char *message)
{
if (msg_id == FSCK_MSG_GITMODULES_MISSING) {
- puts(oid_to_hex(oid));
+ struct fsck_object_report *report = fsck_report;
+ puts(oid_to_hex(report->oid));
return 0;
}
- return fsck_error_function(o, oid, object_type, msg_type, msg_id, message);
+ return fsck_objects_error_function(o, fsck_report,
+ msg_type, msg_id, message);
}