git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 3/3] write_sha1_file_prepare: fix buffer overrun with extra-long object type

From
Eric Sunshine <sunshine@sunshineco.com>
Date
May 4, 2015, 07:25 UTC
Message-ID
<1430724315-524-4-git-send-email-sunshine@sunshineco.com>
In-Reply-To
<1430724315-524-1-git-send-email-sunshine@sunshineco.com>

git-hash-object learned --literally in 5ba9a93 (hash-object: add --literally option, 2014-09-11) which can be used to craft a corrupt/broken object of unknown type. When the user-provided type is particularly long, it can overflow the relatively small stack-based character array handed to write_sha1_file_prepare() by hash_sha1_file() and write_sha1_file(), leading to stack corruption (and crash).

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---

The composed 'hdr' is supposed to be NUL-terminated, and code which accesses 'hdr' expects the NUL to be included as part of its length. Although strbuf ensures that a NUL byte follows the string content, I took the precaution of explicitly adding NUL when formulating 'hdr'

    strbuf_addf(hdr, "%s %lu%c", type, len, '\0');

so that callers can just say hdr.len when the length is needed, rather than having to remember to say hdr.len+1.

I haven't fully convinced myself that this fix is appropriate since it penalizes _all_ callers of hash_sha1_file() and write_sha1_file() with an extra heap allocation (via strbuf), even though "hash-object --literally" is the only mechanism by which an overly-long object type can arrive.

 sha1_file.c            | 21 ++++++++++-----------
 t/t1007-hash-object.sh |  2 +-
 2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/sha1_file.c b/sha1_file.c
index 88f06ba..6d3fa26 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2795,16 +2795,16 @@ void *read_object_with_reference(const unsigned char *sha1,
 
 static void write_sha1_file_prepare(const void *buf, unsigned long len,
                                     const char *type, unsigned char *sha1,
-                                    char *hdr, int *hdrlen)
+				    struct strbuf *hdr)
 {
 	git_SHA_CTX c;
 
 	/* Generate the header */
-	*hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
+	strbuf_addf(hdr, "%s %lu%c", type, len, '\0');
 
 	/* Sha1.. */
 	git_SHA1_Init(&c);
-	git_SHA1_Update(&c, hdr, *hdrlen);
+	git_SHA1_Update(&c, hdr->buf, hdr->len);
 	git_SHA1_Update(&c, buf, len);
 	git_SHA1_Final(sha1, &c);
 }
@@ -2865,9 +2865,8 @@ static int write_buffer(int fd, const void *buf, size_t len)
 int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
-	char hdr[32];
-	int hdrlen;
-	write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+	struct strbuf hdr = STRBUF_INIT;
+	write_sha1_file_prepare(buf, len, type, sha1, &hdr);
 	return 0;
 }
 
@@ -3005,18 +3004,18 @@ static int freshen_packed_object(const unsigned char *sha1)
 int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
 	unsigned char sha1[20];
-	char hdr[32];
-	int hdrlen;
+	struct strbuf hdr = STRBUF_INIT;
 
-	/* Normally if we have it in the pack then we do not bother writing
+	/*
+	 * Normally if we have it in the pack then we do not bother writing
 	 * it out into .git/objects/??/?{38} file.
 	 */
-	write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+	write_sha1_file_prepare(buf, len, type, sha1, &hdr);
 	if (returnsha1)
 		hashcpy(returnsha1, sha1);
 	if (freshen_loose_object(sha1) || freshen_packed_object(sha1))
 		return 0;
-	return write_loose_object(sha1, hdr, hdrlen, buf, len, 0);
+	return write_loose_object(sha1, hdr.buf, hdr.len, buf, len, 0);
 }
 
 int force_object_loose(const unsigned char *sha1, time_t mtime)
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index 0e65577..7c3dcfb 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -206,7 +206,7 @@ test_expect_success '--literally' '
 	echo example | git hash-object -t $t --literally --stdin
 '
 
-test_expect_failure '--literally with extra-long type' '
+test_expect_success '--literally with extra-long type' '
 	t=12345678901234567890123456789012345678901234567890 &&
 	t="$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t$t" &&
 	echo example | git hash-object -t $t --literally --stdin
-- 
2.4.0.319.g7a04823
Previous: Eric SunshineNext: Junio C Hamano
Message 4 of 15 in “hash-object crash fix and new doc and tests”
  1. 0/3 hash-object crash fix and new doc and testsEric Sunshine, May 4, 2015
  2. 1/3 git-hash-object.txt: document --literally optionEric Sunshine, May 4, 2015
  3. 2/3 t1007: add hash-object --literally testsEric Sunshine, May 4, 2015
  4. 3/3 write_sha1_file_prepare: fix buffer overrun with extra-long object typeEric Sunshine, May 4, 2015
  5. Junio C HamanoMay 4, 2015
  6. Junio C HamanoMay 4, 2015
  7. 0/4 "hash-object --literally" fixesJunio C Hamano, May 4, 2015
  8. 1/4 git-hash-object.txt: document --literally optionJunio C Hamano, May 4, 2015
  9. 2/4 write_sha1_file_prepare: fix buffer overrun with extra-long object typeJunio C Hamano, May 4, 2015
  10. Eric SunshineMay 5, 2015
  11. Junio C HamanoMay 5, 2015
  12. Junio C HamanoMay 5, 2015
  13. Eric SunshineMay 5, 2015
  14. 3/4 t1007: add hash-object --literally testsJunio C Hamano, May 4, 2015
  15. 4/4 write_sha1_file(): do not use a separate sha1[] arrayJunio C Hamano, May 4, 2015

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.