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

Re: Stack read out-of-bounds in parse_sha1_header_extended using git 2.10.0

From
Jeff King <peff@peff.net>
Date
Sep 26, 2016, 13:50 UTC
Message-ID
<20160926135055.zeagw6mj3udzhv7s@sigill.intra.peff.net>
In-Reply-To
<xmqqbmzbwmfc.fsf@gitster.mtv.corp.google.com>
On Sun, Sep 25, 2016 at 05:10:31PM -0700, Junio C Hamano wrote:
Show 16 quoted lines
> Gustavo Grieco <gustavo.grieco@imag.fr> writes:
> 
> > We found a stack read out-of-bounds parsing object files using git 2.10.0. It was tested on ArchLinux x86_64. To reproduce, first recompile git with ASAN support and then execute:
> >
> > $ git init ; mkdir -p .git/objects/b2 ; printf 'x' > .git/objects/b2/93584ddd61af21260be75ee9f73e9d53f08cd0
> 
> Interesting.  If you prepare such a broken loose object file in your
> local repository, I would expect that either unpack_sha1_header() or
> unpack_sha1_header_to_strbuf() that sha1_loose_object_info() calls
> would detect and barf by noticing that an error came from libz while
> it attempts to inflate and would not even call parse_sha1_header.
> 
> But it is nevertheless bad to assume that whatever happens to
> inflate without an error must be formatted correctly to allow
> parsing (i.e. has ' ' and then NUL termination within the first 32
> bytes after inflation), which is exactly what the hdr[32] is saying.

Yeah. I also was surprised that we didn't barf on a zlib failure. But based on previous debugging of corrupted zlib data, my recollection is that there are a large number of weird corruptions that zlib will happily pass back and only later complain about a checksum error. So presumably "x" is one of those, and it might not hold for other corruptions (but I didn't try).

Show 6 quoted lines
> Note that this is totally unteseted and not thought through; I
> briefly thought about what unpack_sha1_header_to_strbuf() does with
> this change (it first lets unpack_sha1_header() to attempt with a
> small buffer but it seems to discard the error code from it before
> seeing if the returned buffer has NUL in it); there may be bad
> interactions with it.

Yeah, that seems wrong. I don't think it would involve an out of bounds read, but we probably could fail to correctly report zlib corruption.

Show 27 quoted lines
> diff --git a/sha1_file.c b/sha1_file.c
> index 60ff21f..dfcbd76 100644
> --- a/sha1_file.c
> +++ b/sha1_file.c
> @@ -1648,6 +1648,8 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
>  
>  int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
>  {
> +	int status;
> +
>  	/* Get the data stream */
>  	memset(stream, 0, sizeof(*stream));
>  	stream->next_in = map;
> @@ -1656,7 +1658,15 @@ int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long ma
>  	stream->avail_out = bufsiz;
>  
>  	git_inflate_init(stream);
> -	return git_inflate(stream, 0);
> +	status = git_inflate(stream, 0);
> +	if (status)
> +		return status;
> +
> +	/* Make sure we got the terminating NUL for the object header */
> +	if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
> +		return -1;
> +
> +	return 0;

This doesn't look too invasive as an approach, though I would have done it differently. We're making the assumption that once there is a NUL, the header-parser won't do anything stupid, which creates a coupling between those two bits of code. My inclination would have been to just treat the header as a ptr/len pair, and make sure the parser never reads past the end.

But I implemented that, and it _is_ rather invasive. And it's not like coupling unpack_sha1_header() and parse_sha1_header() is all that terrible; they are meant to be paired.

I haven't read through your follow-up yet; I'll do that before posting my version.

Show 9 quoted lines
>  static int unpack_sha1_header_to_strbuf(git_zstream *stream, unsigned char *map,
> @@ -1758,6 +1768,8 @@ static int parse_sha1_header_extended(const char *hdr, struct object_info *oi,
>  		char c = *hdr++;
>  		if (c == ' ')
>  			break;
> +		if (!c)
> +			die("invalid object header");
>  		type_len++;
>  	}

We keep reading from hdr after this, though I think those bits would all bail correctly on seeing NUL.

-Peff
Previous: Jeff KingNext: Gustavo Grieco
Message 11 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.