diff options
Diffstat (limited to 'fsck.c')
-rw-r--r-- | fsck.c | 306 |
1 files changed, 138 insertions, 168 deletions
@@ -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); } |