diff options
author | Vincent Ambo <tazjin@google.com> | 2020-05-25T23·06+0100 |
---|---|---|
committer | Vincent Ambo <tazjin@google.com> | 2020-05-25T23·06+0100 |
commit | 93ba78d6f4632ef1c5228965e3edc8c0faf88c1e (patch) | |
tree | 85730c182a9f5f492ade8e8ccdb1c2356f9900bd /third_party/git/fsck.c | |
parent | 6f8fbf4aa4b1654ab27d4829e114538761817de0 (diff) |
revert(3p/git): Revert merge of git upstream at v2.26.2 r/852
This causes cgit to serve error pages, which is undesirable. This reverts commit 5229c9b232de5bfa959ad6ebbb4c8192ac513352, reversing changes made to f2b211131f2347342dde63975b09cf603149f1a3.
Diffstat (limited to 'third_party/git/fsck.c')
-rw-r--r-- | third_party/git/fsck.c | 548 |
1 files changed, 210 insertions, 338 deletions
diff --git a/third_party/git/fsck.c b/third_party/git/fsck.c index 73f30773f28a..cdb7d8db0301 100644 --- a/third_party/git/fsck.c +++ b/third_party/git/fsck.c @@ -9,14 +9,12 @@ #include "tag.h" #include "fsck.h" #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 struct oidset gitmodules_found = OIDSET_INIT; @@ -45,17 +43,21 @@ static struct oidset gitmodules_done = OIDSET_INIT; FUNC(MISSING_AUTHOR, ERROR) \ FUNC(MISSING_COMMITTER, ERROR) \ FUNC(MISSING_EMAIL, ERROR) \ + FUNC(MISSING_GRAFT, ERROR) \ FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \ FUNC(MISSING_OBJECT, ERROR) \ + FUNC(MISSING_PARENT, ERROR) \ FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \ FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \ FUNC(MISSING_TAG, ERROR) \ FUNC(MISSING_TAG_ENTRY, ERROR) \ + FUNC(MISSING_TAG_OBJECT, ERROR) \ FUNC(MISSING_TREE, ERROR) \ FUNC(MISSING_TREE_OBJECT, ERROR) \ FUNC(MISSING_TYPE, ERROR) \ FUNC(MISSING_TYPE_ENTRY, ERROR) \ FUNC(MULTIPLE_AUTHORS, ERROR) \ + FUNC(TAG_OBJECT_NOT_TAG, ERROR) \ FUNC(TREE_NOT_SORTED, ERROR) \ FUNC(UNKNOWN_TYPE, ERROR) \ FUNC(ZERO_PADDED_DATE, ERROR) \ @@ -66,7 +68,6 @@ static struct oidset gitmodules_done = OIDSET_INIT; FUNC(GITMODULES_SYMLINK, ERROR) \ FUNC(GITMODULES_URL, ERROR) \ FUNC(GITMODULES_PATH, ERROR) \ - FUNC(GITMODULES_UPDATE, ERROR) \ /* warnings */ \ FUNC(BAD_FILEMODE, WARN) \ FUNC(EMPTY_NAME, WARN) \ @@ -280,16 +281,14 @@ static void append_msg_id(struct strbuf *sb, const char *msg_id) strbuf_addstr(sb, ": "); } -static int object_on_skiplist(struct fsck_options *opts, - const struct object_id *oid) +static int object_on_skiplist(struct fsck_options *opts, struct object *obj) { - return opts && oid && oidset_contains(&opts->skiplist, oid); + return opts && obj && oidset_contains(&opts->skiplist, &obj->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 id, const char *fmt, ...) +__attribute__((format (printf, 4, 5))) +static int report(struct fsck_options *options, struct object *object, + enum fsck_msg_id id, const char *fmt, ...) { va_list ap; struct strbuf sb = STRBUF_INIT; @@ -298,7 +297,7 @@ static int report(struct fsck_options *options, if (msg_type == FSCK_IGNORE) return 0; - if (object_on_skiplist(options, oid)) + if (object_on_skiplist(options, object)) return 0; if (msg_type == FSCK_FATAL) @@ -310,71 +309,49 @@ static int report(struct fsck_options *options, va_start(ap, fmt); strbuf_vaddf(&sb, fmt, ap); - result = options->error_func(options, oid, object_type, - msg_type, sb.buf); + result = options->error_func(options, object, msg_type, sb.buf); strbuf_release(&sb); va_end(ap); return result; } -void fsck_enable_object_names(struct fsck_options *options) +static char *get_object_name(struct fsck_options *options, struct object *obj) { if (!options->object_names) - options->object_names = kh_init_oid_map(); -} - -const char *fsck_get_object_name(struct fsck_options *options, - const struct object_id *oid) -{ - khiter_t pos; - if (!options->object_names) - return NULL; - pos = kh_get_oid_map(options->object_names, *oid); - if (pos >= kh_end(options->object_names)) return NULL; - return kh_value(options->object_names, pos); + return lookup_decoration(options->object_names, obj); } -void fsck_put_object_name(struct fsck_options *options, - const struct object_id *oid, - const char *fmt, ...) +static void put_object_name(struct fsck_options *options, struct object *obj, + const char *fmt, ...) { va_list ap; struct strbuf buf = STRBUF_INIT; - khiter_t pos; - int hashret; + char *existing; if (!options->object_names) return; - - pos = kh_put_oid_map(options->object_names, *oid, &hashret); - if (!hashret) + existing = lookup_decoration(options->object_names, obj); + if (existing) return; va_start(ap, fmt); strbuf_vaddf(&buf, fmt, ap); - kh_value(options->object_names, pos) = strbuf_detach(&buf, NULL); + add_decoration(options->object_names, obj, strbuf_detach(&buf, NULL)); va_end(ap); } -const char *fsck_describe_object(struct fsck_options *options, - const struct object_id *oid) +static const char *describe_object(struct fsck_options *o, struct object *obj) { - static struct strbuf bufs[] = { - STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT - }; - static int b = 0; - struct strbuf *buf; - const char *name = fsck_get_object_name(options, oid); - - buf = bufs + b; - b = (b + 1) % ARRAY_SIZE(bufs); - strbuf_reset(buf); - strbuf_addstr(buf, oid_to_hex(oid)); - if (name) - strbuf_addf(buf, " (%s)", name); + static struct strbuf buf = STRBUF_INIT; + char *name; + + strbuf_reset(&buf); + strbuf_addstr(&buf, oid_to_hex(&obj->oid)); + if (o->object_names && (name = lookup_decoration(o->object_names, obj))) + strbuf_addf(&buf, " (%s)", name); - return buf->buf; + return buf.buf; } static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options) @@ -387,7 +364,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op if (parse_tree(tree)) return -1; - name = fsck_get_object_name(options, &tree->object.oid); + name = get_object_name(options, &tree->object); if (init_tree_desc_gently(&desc, tree->buffer, tree->size)) return -1; while (tree_entry_gently(&desc, &entry)) { @@ -400,21 +377,20 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op if (S_ISDIR(entry.mode)) { obj = (struct object *)lookup_tree(the_repository, &entry.oid); if (name && obj) - fsck_put_object_name(options, &entry.oid, "%s%s/", - name, entry.path); + put_object_name(options, obj, "%s%s/", name, + entry.path); result = options->walk(obj, OBJ_TREE, data, options); } else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) { obj = (struct object *)lookup_blob(the_repository, &entry.oid); if (name && obj) - fsck_put_object_name(options, &entry.oid, "%s%s", - name, entry.path); + put_object_name(options, obj, "%s%s", name, + entry.path); result = options->walk(obj, OBJ_BLOB, data, options); } else { result = error("in tree %s: entry %s has bad mode %.6o", - fsck_describe_object(options, &tree->object.oid), - entry.path, entry.mode); + describe_object(options, &tree->object), entry.path, entry.mode); } if (result < 0) return result; @@ -435,10 +411,10 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio if (parse_commit(commit)) return -1; - name = fsck_get_object_name(options, &commit->object.oid); + name = get_object_name(options, &commit->object); if (name) - fsck_put_object_name(options, get_commit_tree_oid(commit), - "%s:", name); + put_object_name(options, &get_commit_tree(commit)->object, + "%s:", name); result = options->walk((struct object *)get_commit_tree(commit), OBJ_TREE, data, options); @@ -466,17 +442,16 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio while (parents) { if (name) { - struct object_id *oid = &parents->item->object.oid; + struct object *obj = &parents->item->object; if (counter++) - fsck_put_object_name(options, oid, "%s^%d", - name, counter); + put_object_name(options, obj, "%s^%d", + name, counter); else if (generation > 0) - fsck_put_object_name(options, oid, "%.*s~%d", - name_prefix_len, name, - generation + 1); + put_object_name(options, obj, "%.*s~%d", + name_prefix_len, name, generation + 1); else - fsck_put_object_name(options, oid, "%s^", name); + put_object_name(options, obj, "%s^", name); } result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options); if (result < 0) @@ -490,12 +465,12 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options) { - const char *name = fsck_get_object_name(options, &tag->object.oid); + char *name = get_object_name(options, &tag->object); if (parse_tag(tag)) return -1; if (name) - fsck_put_object_name(options, &tag->tagged->oid, "%s", name); + put_object_name(options, tag->tagged, "%s", name); return options->walk(tag->tagged, OBJ_ANY, data, options); } @@ -517,8 +492,7 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) case OBJ_TAG: return fsck_walk_tag((struct tag *)obj, data, options); default: - error("Unknown object type for %s", - fsck_describe_object(options, &obj->oid)); + error("Unknown object type for %s", describe_object(options, obj)); return -1; } } @@ -569,9 +543,7 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con return c1 < c2 ? 0 : TREE_UNORDERED; } -static int fsck_tree(const struct object_id *oid, - const char *buffer, unsigned long size, - struct fsck_options *options) +static int fsck_tree(struct tree *item, struct fsck_options *options) { int retval = 0; int has_null_sha1 = 0; @@ -588,8 +560,8 @@ static int fsck_tree(const struct object_id *oid, unsigned o_mode; const char *o_name; - if (init_tree_desc_gently(&desc, buffer, size)) { - retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); + if (init_tree_desc_gently(&desc, item->buffer, item->size)) { + retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); return retval; } @@ -598,7 +570,7 @@ static int fsck_tree(const struct object_id *oid, while (desc.size) { unsigned short mode; - const char *name, *backslash; + const char *name; const struct object_id *oid; oid = tree_entry_extract(&desc, &name, &mode); @@ -615,30 +587,13 @@ static int fsck_tree(const struct object_id *oid, if (!S_ISLNK(mode)) oidset_insert(&gitmodules_found, oid); else - retval += report(options, - oid, OBJ_TREE, + retval += report(options, &item->object, FSCK_MSG_GITMODULES_SYMLINK, ".gitmodules is a symbolic link"); } - if ((backslash = strchr(name, '\\'))) { - while (backslash) { - backslash++; - has_dotgit |= is_ntfs_dotgit(backslash); - if (is_ntfs_dotgitmodules(backslash)) { - if (!S_ISLNK(mode)) - oidset_insert(&gitmodules_found, oid); - else - retval += report(options, oid, OBJ_TREE, - FSCK_MSG_GITMODULES_SYMLINK, - ".gitmodules is a symbolic link"); - } - backslash = strchr(backslash, '\\'); - } - } - if (update_tree_entry_gently(&desc)) { - retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); + retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); break; } @@ -683,31 +638,30 @@ static int fsck_tree(const struct object_id *oid, } if (has_null_sha1) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); + retval += report(options, &item->object, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); if (has_full_path) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_FULL_PATHNAME, "contains full pathnames"); + retval += report(options, &item->object, FSCK_MSG_FULL_PATHNAME, "contains full pathnames"); if (has_empty_name) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_EMPTY_NAME, "contains empty pathname"); + retval += report(options, &item->object, FSCK_MSG_EMPTY_NAME, "contains empty pathname"); if (has_dot) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOT, "contains '.'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOT, "contains '.'"); if (has_dotdot) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTDOT, "contains '..'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOTDOT, "contains '..'"); if (has_dotgit) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTGIT, "contains '.git'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOTGIT, "contains '.git'"); if (has_zero_pad) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes"); + retval += report(options, &item->object, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes"); if (has_bad_modes) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_FILEMODE, "contains bad file modes"); + retval += report(options, &item->object, FSCK_MSG_BAD_FILEMODE, "contains bad file modes"); if (has_dup_entries) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries"); + retval += report(options, &item->object, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries"); if (not_properly_sorted) - retval += report(options, oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); + retval += report(options, &item->object, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); return retval; } static int verify_headers(const void *data, unsigned long size, - const struct object_id *oid, enum object_type type, - struct fsck_options *options) + struct object *obj, struct fsck_options *options) { const char *buffer = (const char *)data; unsigned long i; @@ -715,7 +669,7 @@ static int verify_headers(const void *data, unsigned long size, for (i = 0; i < size; i++) { switch (buffer[i]) { case '\0': - return report(options, oid, type, + return report(options, obj, FSCK_MSG_NUL_IN_HEADER, "unterminated header: NUL at offset %ld", i); case '\n': @@ -733,13 +687,11 @@ static int verify_headers(const void *data, unsigned long size, if (size && buffer[size - 1] == '\n') return 0; - return report(options, oid, type, + return report(options, obj, FSCK_MSG_UNTERMINATED_HEADER, "unterminated header"); } -static int fsck_ident(const char **ident, - const struct object_id *oid, enum object_type type, - struct fsck_options *options) +static int fsck_ident(const char **ident, struct object *obj, struct fsck_options *options) { const char *p = *ident; char *end; @@ -749,28 +701,28 @@ static int fsck_ident(const char **ident, (*ident)++; if (*p == '<') - return report(options, oid, type, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + return report(options, obj, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); p += strcspn(p, "<>\n"); if (*p == '>') - return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); + return report(options, obj, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); if (*p != '<') - return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); + return report(options, obj, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); if (p[-1] != ' ') - return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); p++; p += strcspn(p, "<>\n"); if (*p != '>') - return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); + return report(options, obj, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); p++; if (*p != ' ') - return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); + return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); p++; if (*p == '0' && p[1] != ' ') - return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); + return report(options, obj, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); if (date_overflows(parse_timestamp(p, &end, 10))) - return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); + return report(options, obj, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); if ((end == p || *end != ' ')) - return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); + return report(options, obj, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); p = end + 1; if ((*p != '+' && *p != '-') || !isdigit(p[1]) || @@ -778,60 +730,83 @@ static int fsck_ident(const char **ident, !isdigit(p[3]) || !isdigit(p[4]) || (p[5] != '\n')) - return report(options, oid, type, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone"); + return report(options, obj, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone"); p += 6; return 0; } -static int fsck_commit(const struct object_id *oid, - const char *buffer, unsigned long size, - struct fsck_options *options) +static int fsck_commit_buffer(struct commit *commit, const char *buffer, + unsigned long size, struct fsck_options *options) { - struct object_id tree_oid, parent_oid; - unsigned author_count; + struct object_id tree_oid, oid; + struct commit_graft *graft; + unsigned parent_count, parent_line_count = 0, author_count; int err; const char *buffer_begin = buffer; const char *p; - if (verify_headers(buffer, size, oid, OBJ_COMMIT, options)) + if (verify_headers(buffer, size, &commit->object, options)) return -1; if (!skip_prefix(buffer, "tree ", &buffer)) - return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); + return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') { - err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); + err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); if (err) return err; } buffer = p + 1; while (skip_prefix(buffer, "parent ", &buffer)) { - if (parse_oid_hex(buffer, &parent_oid, &p) || *p != '\n') { - err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); + if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') { + err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); if (err) return err; } buffer = p + 1; + parent_line_count++; + } + graft = lookup_commit_graft(the_repository, &commit->object.oid); + parent_count = commit_list_count(commit->parents); + if (graft) { + if (graft->nr_parent == -1 && !parent_count) + ; /* shallow commit */ + else if (graft->nr_parent != parent_count) { + err = report(options, &commit->object, FSCK_MSG_MISSING_GRAFT, "graft objects missing"); + if (err) + return err; + } + } else { + if (parent_count != parent_line_count) { + err = report(options, &commit->object, FSCK_MSG_MISSING_PARENT, "parent objects missing"); + if (err) + return err; + } } author_count = 0; while (skip_prefix(buffer, "author ", &buffer)) { author_count++; - err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); + err = fsck_ident(&buffer, &commit->object, options); if (err) return err; } if (author_count < 1) - err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line"); + err = report(options, &commit->object, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line"); else if (author_count > 1) - err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); + err = report(options, &commit->object, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); if (err) return err; if (!skip_prefix(buffer, "committer ", &buffer)) - return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); - err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); + return report(options, &commit->object, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); + err = fsck_ident(&buffer, &commit->object, options); if (err) return err; + if (!get_commit_tree(commit)) { + err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", oid_to_hex(&tree_oid)); + if (err) + return err; + } if (memchr(buffer_begin, '\0', size)) { - err = report(options, oid, OBJ_COMMIT, FSCK_MSG_NUL_IN_COMMIT, + err = report(options, &commit->object, FSCK_MSG_NUL_IN_COMMIT, "NUL byte in the commit object body"); if (err) return err; @@ -839,60 +814,91 @@ static int fsck_commit(const struct object_id *oid, return 0; } -static int fsck_tag(const struct object_id *oid, const char *buffer, - unsigned long size, struct fsck_options *options) +static int fsck_commit(struct commit *commit, const char *data, + unsigned long size, struct fsck_options *options) { - struct object_id tagged_oid; + const char *buffer = data ? data : get_commit_buffer(commit, &size); + int ret = fsck_commit_buffer(commit, buffer, size, options); + if (!data) + unuse_commit_buffer(commit, buffer); + return ret; +} + +static int fsck_tag_buffer(struct tag *tag, const char *data, + unsigned long size, struct fsck_options *options) +{ + struct object_id oid; int ret = 0; - char *eol; + const char *buffer; + char *to_free = NULL, *eol; struct strbuf sb = STRBUF_INIT; const char *p; - ret = verify_headers(buffer, size, oid, OBJ_TAG, options); + if (data) + buffer = data; + else { + enum object_type type; + + buffer = to_free = + read_object_file(&tag->object.oid, &type, &size); + if (!buffer) + return report(options, &tag->object, + FSCK_MSG_MISSING_TAG_OBJECT, + "cannot read tag object"); + + if (type != OBJ_TAG) { + ret = report(options, &tag->object, + FSCK_MSG_TAG_OBJECT_NOT_TAG, + "expected tag got %s", + type_name(type)); + goto done; + } + } + + ret = verify_headers(buffer, size, &tag->object, options); if (ret) goto done; if (!skip_prefix(buffer, "object ", &buffer)) { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); goto done; } - if (parse_oid_hex(buffer, &tagged_oid, &p) || *p != '\n') { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1"); + if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') { + ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1"); if (ret) goto done; } buffer = p + 1; if (!skip_prefix(buffer, "type ", &buffer)) { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); goto done; } if (type_from_string_gently(buffer, eol - buffer, 1) < 0) - ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_TYPE, "invalid 'type' value"); + ret = report(options, &tag->object, FSCK_MSG_BAD_TYPE, "invalid 'type' value"); if (ret) goto done; buffer = eol + 1; if (!skip_prefix(buffer, "tag ", &buffer)) { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); goto done; } strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer); if (check_refname_format(sb.buf, 0)) { - ret = report(options, oid, OBJ_TAG, - FSCK_MSG_BAD_TAG_NAME, - "invalid 'tag' name: %.*s", - (int)(eol - buffer), buffer); + ret = report(options, &tag->object, FSCK_MSG_BAD_TAG_NAME, + "invalid 'tag' name: %.*s", + (int)(eol - buffer), buffer); if (ret) goto done; } @@ -900,163 +906,32 @@ static int fsck_tag(const struct object_id *oid, const char *buffer, if (!skip_prefix(buffer, "tagger ", &buffer)) { /* early tags do not contain 'tagger' lines; warn only */ - ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); if (ret) goto done; } else - ret = fsck_ident(&buffer, oid, OBJ_TAG, options); + ret = fsck_ident(&buffer, &tag->object, options); done: strbuf_release(&sb); + free(to_free); return ret; } -/* - * Like builtin/submodule--helper.c's starts_with_dot_slash, but without - * relying on the platform-dependent is_dir_sep helper. - * - * This is for use in checking whether a submodule URL is interpreted as - * relative to the current directory on any platform, since \ is a - * directory separator on Windows but not on other platforms. - */ -static int starts_with_dot_slash(const char *str) -{ - return str[0] == '.' && (str[1] == '/' || str[1] == '\\'); -} - -/* - * Like starts_with_dot_slash, this is a variant of submodule--helper's - * helper of the same name with the twist that it accepts backslash as a - * directory separator even on non-Windows platforms. - */ -static int starts_with_dot_dot_slash(const char *str) -{ - return str[0] == '.' && starts_with_dot_slash(str + 1); -} - -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) +static int fsck_tag(struct tag *tag, const char *data, + unsigned long size, struct fsck_options *options) { - /* - * 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; -} + struct object *tagged = tag->tagged; -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)) { - 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; - } + if (!tagged) + return report(options, &tag->object, FSCK_MSG_BAD_TAG_OBJECT, "could not load tagged object"); - 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; + return fsck_tag_buffer(tag, data, size, options); } struct fsck_gitmodules_data { - const struct object_id *oid; + struct object *obj; struct fsck_options *options; int ret; }; @@ -1074,47 +949,38 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) name = xmemdupz(subsection, subsection_len); if (check_submodule_name(name) < 0) - data->ret |= report(data->options, - data->oid, OBJ_BLOB, + data->ret |= report(data->options, data->obj, FSCK_MSG_GITMODULES_NAME, "disallowed submodule name: %s", name); if (!strcmp(key, "url") && value && - check_submodule_url(value) < 0) - data->ret |= report(data->options, - data->oid, OBJ_BLOB, + looks_like_command_line_option(value)) + data->ret |= report(data->options, data->obj, FSCK_MSG_GITMODULES_URL, "disallowed submodule url: %s", value); if (!strcmp(key, "path") && value && looks_like_command_line_option(value)) - data->ret |= report(data->options, - data->oid, OBJ_BLOB, + data->ret |= report(data->options, data->obj, FSCK_MSG_GITMODULES_PATH, "disallowed submodule path: %s", value); - if (!strcmp(key, "update") && value && - parse_submodule_update_type(value) == SM_UPDATE_COMMAND) - data->ret |= report(data->options, data->oid, OBJ_BLOB, - FSCK_MSG_GITMODULES_UPDATE, - "disallowed submodule update setting: %s", - value); free(name); return 0; } -static int fsck_blob(const struct object_id *oid, const char *buf, +static int fsck_blob(struct blob *blob, const char *buf, unsigned long size, struct fsck_options *options) { struct fsck_gitmodules_data data; struct config_options config_opts = { 0 }; - if (!oidset_contains(&gitmodules_found, oid)) + if (!oidset_contains(&gitmodules_found, &blob->object.oid)) return 0; - oidset_insert(&gitmodules_done, oid); + oidset_insert(&gitmodules_done, &blob->object.oid); - if (object_on_skiplist(options, oid)) + if (object_on_skiplist(options, &blob->object)) return 0; if (!buf) { @@ -1123,18 +989,18 @@ static int fsck_blob(const struct object_id *oid, const char *buf, * blob too gigantic to load into memory. Let's just consider * that an error. */ - return report(options, oid, OBJ_BLOB, + return report(options, &blob->object, FSCK_MSG_GITMODULES_LARGE, ".gitmodules too large to parse"); } - data.oid = oid; + data.obj = &blob->object; data.options = options; data.ret = 0; config_opts.error_action = CONFIG_ERROR_SILENT; if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB, ".gitmodules", buf, size, &data, &config_opts)) - data.ret |= report(options, oid, OBJ_BLOB, + data.ret |= report(options, &blob->object, FSCK_MSG_GITMODULES_PARSE, "could not parse gitmodules blob"); @@ -1145,33 +1011,31 @@ int fsck_object(struct object *obj, void *data, unsigned long size, struct fsck_options *options) { if (!obj) - return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); + return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); if (obj->type == OBJ_BLOB) - return fsck_blob(&obj->oid, data, size, options); + return fsck_blob((struct blob *)obj, data, size, options); if (obj->type == OBJ_TREE) - return fsck_tree(&obj->oid, data, size, options); + return fsck_tree((struct tree *) obj, options); if (obj->type == OBJ_COMMIT) - return fsck_commit(&obj->oid, data, size, options); + return fsck_commit((struct commit *) obj, (const char *) data, + size, options); if (obj->type == OBJ_TAG) - return fsck_tag(&obj->oid, data, size, options); + return fsck_tag((struct tag *) obj, (const char *) data, + size, options); - return report(options, &obj->oid, obj->type, - FSCK_MSG_UNKNOWN_TYPE, - "unknown type '%d' (internal fsck error)", - obj->type); + return report(options, obj, FSCK_MSG_UNKNOWN_TYPE, "unknown type '%d' (internal fsck error)", + obj->type); } int fsck_error_function(struct fsck_options *o, - const struct object_id *oid, - enum object_type object_type, - int msg_type, const char *message) + struct object *obj, int msg_type, const char *message) { if (msg_type == FSCK_WARN) { - warning("object %s: %s", fsck_describe_object(o, oid), message); + warning("object %s: %s", describe_object(o, obj), message); return 0; } - error("object %s: %s", fsck_describe_object(o, oid), message); + error("object %s: %s", describe_object(o, obj), message); return 1; } @@ -1183,6 +1047,7 @@ int fsck_finish(struct fsck_options *options) oidset_iter_init(&gitmodules_found, &iter); while ((oid = oidset_iter_next(&iter))) { + struct blob *blob; enum object_type type; unsigned long size; char *buf; @@ -1190,22 +1055,29 @@ int fsck_finish(struct fsck_options *options) if (oidset_contains(&gitmodules_done, oid)) continue; + blob = lookup_blob(the_repository, oid); + if (!blob) { + struct object *obj = lookup_unknown_object(oid); + ret |= report(options, obj, + FSCK_MSG_GITMODULES_BLOB, + "non-blob found at .gitmodules"); + continue; + } + buf = read_object_file(oid, &type, &size); if (!buf) { - if (is_promisor_object(oid)) + if (is_promisor_object(&blob->object.oid)) continue; - ret |= report(options, - oid, OBJ_BLOB, + ret |= report(options, &blob->object, FSCK_MSG_GITMODULES_MISSING, "unable to read .gitmodules blob"); continue; } if (type == OBJ_BLOB) - ret |= fsck_blob(oid, buf, size, options); + ret |= fsck_blob(blob, buf, size, options); else - ret |= report(options, - oid, type, + ret |= report(options, &blob->object, FSCK_MSG_GITMODULES_BLOB, "non-blob found at .gitmodules"); free(buf); |