diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index 73bb939359..dbc46d14e3 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -171,6 +171,11 @@ JGit, libgit2 and Gitoxide need to support it. matches the default branch name used in new repositories by many of the big Git forges. +* Git will accept hex object IDs only in lowercase. The fact that Git has + historically allowed uppercase characters in hex object IDs has been the + source of a variety of bugs and security problems in software using Git. We + don't expect most users to notice any change. + * Git will require Rust as a mandatory part of the build process. While Git already started to adopt Rust in Git 2.49, all parts written in Rust are optional for the time being. This includes: diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 6b2a87e2d3..44ac72ef1e 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1866,7 +1866,7 @@ static void repack_local_links(void) while (strbuf_getline_lf(&line, out) != EOF) { unsigned char binary[GIT_MAX_RAWSZ]; if (line.len != the_hash_algo->hexsz || - !hex_to_bytes(binary, line.buf, line.len)) + !hex_to_bytes(binary, line.buf, line.len, HEX_KIND_MIXED)) die(_("index-pack: Expecting full hex object ID lines only from pack-objects.")); /* diff --git a/color.c b/color.c index 00b53f97ac..9015d0faf1 100644 --- a/color.c +++ b/color.c @@ -72,7 +72,7 @@ static int get_hex_color(const char **inp, int width, unsigned char *out) unsigned int val; assert(width == 1 || width == 2); - val = (hexval(in[0]) << 4) | hexval(in[width - 1]); + val = (hexval(in[0], HEX_KIND_MIXED) << 4) | hexval(in[width - 1], HEX_KIND_MIXED); if (val & ~0xff) return -1; *inp += width; diff --git a/diagnose.c b/diagnose.c index 5092bf80d3..9c652d36a6 100644 --- a/diagnose.c +++ b/diagnose.c @@ -112,7 +112,7 @@ static void loose_objs_stats(struct strbuf *buf, const char *path) while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) if (get_dtype(e, &count_path, 0) == DT_DIR && strlen(e->d_name) == 2 && - !hex_to_bytes(&c, e->d_name, 1)) { + !hex_to_bytes(&c, e->d_name, 1, HEX_KIND_OID)) { strbuf_setlen(&count_path, base_path_len); strbuf_addf(&count_path, "%s/", e->d_name); total += (count = count_files(&count_path)); diff --git a/hex-ll.c b/hex-ll.c index 4d7ece1de5..b2e9684693 100644 --- a/hex-ll.c +++ b/hex-ll.c @@ -36,10 +36,45 @@ const signed char hexval_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ }; -int hex_to_bytes(unsigned char *binary, const char *hex, size_t len) +const signed char hexval_lc_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; + +int hex_to_bytes(unsigned char *binary, const char *hex, size_t len, enum hexkind kind) { for (; len; len--, hex += 2) { - unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + unsigned int val = (hexval(hex[0], kind) << 4) | hexval(hex[1], kind); if (val & ~0xff) return -1; diff --git a/hex-ll.h b/hex-ll.h index a381fa8556..2f9c8d7c25 100644 --- a/hex-ll.h +++ b/hex-ll.h @@ -1,20 +1,32 @@ #ifndef HEX_LL_H #define HEX_LL_H +enum hexkind { + HEX_KIND_MIXED = 0, + HEX_KIND_LOWER = 1, +}; + +#ifdef WITH_BREAKING_CHANGES +#define HEX_KIND_OID HEX_KIND_LOWER +#else +#define HEX_KIND_OID HEX_KIND_MIXED +#endif + extern const signed char hexval_table[256]; -static inline unsigned int hexval(unsigned char c) +extern const signed char hexval_lc_table[256]; +static inline unsigned int hexval(unsigned char c, enum hexkind kind) { - return hexval_table[c]; + return kind == HEX_KIND_MIXED ? hexval_table[c] : hexval_lc_table[c]; } /* * Convert two consecutive hexadecimal digits into a char. Return a * negative value on error. Don't run over the end of short strings. */ -static inline int hex2chr(const char *s) +static inline int hex2chr(const char *s, enum hexkind kind) { - unsigned int val = hexval(s[0]); - return (val & ~0xf) ? val : (val << 4) | hexval(s[1]); + unsigned int val = hexval(s[0], kind); + return (val & ~0xf) ? val : (val << 4) | hexval(s[1], kind); } /* @@ -22,6 +34,6 @@ static inline int hex2chr(const char *s) * values to `binary` as `len` bytes. Return 0 on success, or -1 if * the input does not consist of hex digits). */ -int hex_to_bytes(unsigned char *binary, const char *hex, size_t len); +int hex_to_bytes(unsigned char *binary, const char *hex, size_t len, enum hexkind kind); #endif diff --git a/hex.c b/hex.c index f02832140d..4e1e81af3f 100644 --- a/hex.c +++ b/hex.c @@ -9,7 +9,7 @@ static int get_hash_hex_algop(const char *hex, unsigned char *hash, const struct git_hash_algo *algop) { for (size_t i = 0; i < algop->rawsz; i++) { - int val = hex2chr(hex); + int val = hex2chr(hex, HEX_KIND_OID); if (val < 0) return -1; *hash++ = val; diff --git a/http-push.c b/http-push.c index b8f3faaed9..43b4b61c70 100644 --- a/http-push.c +++ b/http-push.c @@ -1031,12 +1031,13 @@ static int get_oid_hex_from_objpath(const char *path, struct object_id *oid) if (strlen(path) != the_hash_algo->hexsz + 1) return -1; - if (hex_to_bytes(oid->hash, path, 1)) + if (hex_to_bytes(oid->hash, path, 1, HEX_KIND_OID)) return -1; path += 2; path++; /* skip '/' */ - return hex_to_bytes(oid->hash + 1, path, the_hash_algo->rawsz - 1); + return hex_to_bytes(oid->hash + 1, path, the_hash_algo->rawsz - 1, + HEX_KIND_OID); } static void process_ls_object(struct remote_ls_ctx *ls) diff --git a/mailinfo.c b/mailinfo.c index 13949ff31e..85c3119048 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -396,7 +396,7 @@ static int decode_q_segment(struct strbuf *out, const struct strbuf *q_seg, int ch, d = *in; if (d == '\n' || !d) break; /* drop trailing newline */ - ch = hex2chr(in); + ch = hex2chr(in, HEX_KIND_MIXED); if (ch >= 0) { strbuf_addch(out, ch); in += 2; diff --git a/notes.c b/notes.c index ec9c2cb150..7e9e3eb2d2 100644 --- a/notes.c +++ b/notes.c @@ -428,7 +428,7 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, goto handle_non_note; if (hex_to_bytes(object_oid.hash + prefix_len, entry.path, - hashsz - prefix_len)) + hashsz - prefix_len, HEX_KIND_MIXED)) goto handle_non_note; /* entry.path is not a SHA1 */ memset(object_oid.hash + hashsz, 0, GIT_MAX_RAWSZ - hashsz); @@ -442,7 +442,8 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, /* internal nodes must be trees */ goto handle_non_note; - if (hex_to_bytes(object_oid.hash + len++, entry.path, 1)) + if (hex_to_bytes(object_oid.hash + len++, entry.path, 1, + HEX_KIND_OID)) goto handle_non_note; /* entry.path is not a SHA1 */ /* diff --git a/object-file.c b/object-file.c index 9123593126..d72c77cab6 100644 --- a/object-file.c +++ b/object-file.c @@ -1081,7 +1081,7 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr, strbuf_add(path, de->d_name, namelen); if (namelen == algop->hexsz - 2 && !hex_to_bytes(oid.hash + 1, de->d_name, - algop->rawsz - 1)) { + algop->rawsz - 1, HEX_KIND_OID)) { oid_set_algo(&oid, algop); memset(oid.hash + algop->rawsz, 0, GIT_MAX_RAWSZ - algop->rawsz); diff --git a/object-name.c b/object-name.c index 4eda8c8eac..d6da559e0e 100644 --- a/object-name.c +++ b/object-name.c @@ -236,17 +236,10 @@ static int parse_oid_prefix(const char *name, int len, { for (int i = 0; i < len; i++) { unsigned char c = name[i]; - unsigned char val; - if (c >= '0' && c <= '9') { - val = c - '0'; - } else if (c >= 'a' && c <= 'f') { - val = c - 'a' + 10; - } else if (c >= 'A' && c <='F') { - val = c - 'A' + 10; - c -= 'A' - 'a'; - } else { + int val = hexval(c, HEX_KIND_OID); + + if (val < 0) return -1; - } if (hex_out) hex_out[i] = c; diff --git a/pkt-line.c b/pkt-line.c index 3fc3e9ea70..338075558c 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -378,10 +378,10 @@ int packet_length(const char lenbuf_hex[4], size_t size) { if (size < 4) BUG("buffer too small"); - return hexval(lenbuf_hex[0]) << 12 | - hexval(lenbuf_hex[1]) << 8 | - hexval(lenbuf_hex[2]) << 4 | - hexval(lenbuf_hex[3]); + return hexval(lenbuf_hex[0], HEX_KIND_MIXED) << 12 | + hexval(lenbuf_hex[1], HEX_KIND_MIXED) << 8 | + hexval(lenbuf_hex[2], HEX_KIND_MIXED) << 4 | + hexval(lenbuf_hex[3], HEX_KIND_MIXED); } static const char *find_packfile_uri_path(const char *buffer) diff --git a/ref-filter.c b/ref-filter.c index bdf54f6f59..cd02677e42 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -3636,7 +3636,7 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting if (cp[1] == '%') cp++; else { - int ch = hex2chr(cp + 1); + int ch = hex2chr(cp + 1, HEX_KIND_MIXED); if (0 <= ch) { strbuf_addch(s, ch); cp += 3; diff --git a/strbuf.c b/strbuf.c index 44955669e8..88d23f8ac5 100644 --- a/strbuf.c +++ b/strbuf.c @@ -457,7 +457,7 @@ size_t strbuf_expand_literal(struct strbuf *sb, const char *placeholder) return 1; case 'x': /* %x00 == NUL, %x0a == LF, etc. */ - ch = hex2chr(placeholder + 1); + ch = hex2chr(placeholder + 1, HEX_KIND_MIXED); if (ch < 0) return 0; strbuf_addch(sb, ch); diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index 87638a4a2c..f07b45de5a 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -60,6 +60,11 @@ test_expect_success 'works with one good rev' ' test "$rev_head" = "$HASH4" ' +test_expect_success WITH_BREAKING_CHANGES 'rejects uppercase revs' ' + UC_HASH=$(echo "$HASH1" | tr a-f A-F) && + test_must_fail git rev-parse --verify "$UC_HASH" +' + test_expect_success 'fails with any bad rev or many good revs' ' test_must_fail git rev-parse --verify 2>error && test_grep "single revision" error && diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh index bf7ba0e558..29db815c77 100755 --- a/t/t5324-split-commit-graph.sh +++ b/t/t5324-split-commit-graph.sh @@ -349,7 +349,7 @@ test_expect_success 'verify after commit-graph-chain corruption (base)' ' test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_grep "invalid commit-graph chain" err && - corrupt_file "$graphdir/commit-graph-chain" 30 "A" && + corrupt_file "$graphdir/commit-graph-chain" 30 "a" && test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_grep "unable to find all commit-graph files" err @@ -364,7 +364,7 @@ test_expect_success 'verify after commit-graph-chain corruption (tip)' ' test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_grep "invalid commit-graph chain" err && - corrupt_file "$graphdir/commit-graph-chain" 70 "A" && + corrupt_file "$graphdir/commit-graph-chain" 70 "b" && test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_grep "unable to find all commit-graph files" err diff --git a/url.c b/url.c index a59818278f..b4d72f784a 100644 --- a/url.c +++ b/url.c @@ -62,7 +62,7 @@ static char *url_decode_internal(const char **query, int len, } if (c == '%' && (len < 0 || len >= 3)) { - int val = hex2chr(q + 1); + int val = hex2chr(q + 1, HEX_KIND_MIXED); if (0 < val) { strbuf_addch(out, val); q += 3; diff --git a/urlmatch.c b/urlmatch.c index 20bc2d009c..989f1d794b 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -50,7 +50,7 @@ static int append_normalized_escapes(struct strbuf *buf, if (ch == '%') { if (from_len < 2) return 0; - ch = hex2chr(from); + ch = hex2chr(from, HEX_KIND_MIXED); if (ch < 0) return 0; from += 2;