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

Re: [PATCH] unpack_sha1_header(): detect malformed object header

From
Junio C Hamano <gitster@pobox.com>
Date
Sep 26, 2016, 17:34 UTC
Message-ID
<xmqqoa3atviv.fsf@gitster.mtv.corp.google.com>
In-Reply-To
<xmqqfuomvdqe.fsf@gitster.mtv.corp.google.com>

And the second one, that no longer checks SP in unpacker, looks like this.

-- >8 --
Subject: [PATCH] unpack_sha1_header(): detect malformed object header
When opening a loose object file, we often do this sequence:
 - prepare a short buffer for the object header (on stack)
 - call unpack_sha1_header() and have early part of the object data
   inflated, enough to fill the buffer
 - parse that data in the short buffer, assuming that the first part
   of the object is <typename> SP <length> NUL

Because the parsing function parse_sha1_header_extended() is not given the number of bytes inflated into the header buffer, it you craft a file whose early part inflates a garbage sequence without SP or NUL, and replace a loose object with it, it will end up reading past the end of the inflated data.

To correct this, do the following four things:
 - rename unpack_sha1_header() to unpack_sha1_short_header() and
   have unpack_sha1_header_to_strbuf() keep calling that as its
   helper function.  This will detect and report zlib errors, but is
   not aware of the format of a loose object (as before).
 - introduce unpack_sha1_header() that calls the same helper
   function, and when zlib reports it inflated OK into the buffer,
   check if the inflated data has NUL.  This would ensure that
   parsing function will terminate within the buffer that holds the
   inflated header.
 - update unpack_sha1_header_to_strbuf() to check if the resulting
   buffer has NUL for the same effect.
 - update parse_sha1_header_extended() to make sure that its loop to
   find the SP that terminates the <typename> stops at NUL.

Essentially, this makes unpack_*() functions that are asked to unpack a loose object header to be a bit more strict and detect an input that cannot possibly be a valid object header, even before the parsing function kicks in.

Reported-by: Gustavo Grieco <gustavo.grieco@imag.fr>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 sha1_file.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/sha1_file.c b/sha1_file.c
index 17262e1..f7054d3 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -1566,7 +1566,9 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
 	return used;
 }
 
-int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+static int unpack_sha1_short_header(git_zstream *stream,
+				    unsigned char *map, unsigned long mapsize,
+				    void *buffer, unsigned long bufsiz)
 {
 	/* Get the data stream */
 	memset(stream, 0, sizeof(*stream));
@@ -1579,13 +1581,31 @@ int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long ma
 	return git_inflate(stream, 0);
 }
 
+int unpack_sha1_header(git_zstream *stream,
+		       unsigned char *map, unsigned long mapsize,
+		       void *buffer, unsigned long bufsiz)
+{
+	int status = unpack_sha1_short_header(stream, map, mapsize,
+					      buffer, bufsiz);
+
+	if (status < Z_OK)
+		return status;
+
+	/* Make sure we have the terminating NUL */
+	if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
+		return -1;
+	return 0;
+}
+
 static int unpack_sha1_header_to_strbuf(git_zstream *stream, unsigned char *map,
 					unsigned long mapsize, void *buffer,
 					unsigned long bufsiz, struct strbuf *header)
 {
 	int status;
 
-	status = unpack_sha1_header(stream, map, mapsize, buffer, bufsiz);
+	status = unpack_sha1_short_header(stream, map, mapsize, buffer, bufsiz);
+	if (status < Z_OK)
+		return -1;
 
 	/*
 	 * Check if entire header is unpacked in the first iteration.
@@ -1676,6 +1696,8 @@ static int parse_sha1_header_extended(const char *hdr, struct object_info *oi,
 	 */
 	for (;;) {
 		char c = *hdr++;
+		if (!c)
+			return -1;
 		if (c == ' ')
 			break;
 		type_len++;
-- 
2.10.0-533-ga18d90d
Previous: Junio C HamanoNext: Jeff King
Message 9 of 22 in “Stack read out-of-bounds in parse_sha1_header_extended using git 2.10.0”
  1. Gustavo GriecoSep 25, 2016
  2. Junio C HamanoSep 26, 2016
  3. unpack_sha1_header(): detect malformed object headerJunio C Hamano, Sep 26, 2016
  4. Jeff KingSep 26, 2016
  5. Junio C HamanoSep 26, 2016
  6. Junio C HamanoSep 26, 2016
  7. Jeff KingSep 26, 2016
  8. Junio C HamanoSep 26, 2016
  9. Junio C HamanoSep 26, 2016
  10. Jeff KingSep 26, 2016
  11. Jeff KingSep 26, 2016
  12. Gustavo GriecoSep 26, 2016
  13. Junio C HamanoSep 26, 2016
  14. Gustavo GriecoSep 26, 2016
  15. Junio C HamanoSep 26, 2016
  16. Junio C HamanoSep 26, 2016
  17. Gustavo GriecoSep 27, 2016
  18. Jeff KingSep 27, 2016
  19. Possible integer overflow parsing malformed objects in git 2.10.0Gustavo Grieco, Sep 27, 2016
  20. Jeff KingSep 27, 2016
  21. Junio C HamanoSep 27, 2016
  22. Gustavo GriecoSep 27, 2016

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.