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

[PATCH 1/3] untracked-cache: be defensive about missing NULs in index

From
Jeff King <peff@peff.net>
Date
Apr 18, 2019, 21:17 UTC
Message-ID
<20190418211701.GA18520@sigill.intra.peff.net>
In-Reply-To
<20190418211408.GA18011@sigill.intra.peff.net>

The on-disk format for the untracked-cache extension contains NUL-terminated filenames. We parse these from the mmap'd file using string functions like strlen(). This works fine in the normal case, but if we see a malformed or corrupted index, we might read off the end of our mmap.

Instead, let's use memchr() to find the trailing NUL within the bytes we know are available, and return an error if it's missing.

Note that we can further simplify by folding another range check into our conditional. After we find the end of the string, we set "next" to the byte after the string and treat it as an error if there are no such bytes left. That saves us from having to do a range check at the beginning of each subsequent string (and works because there is always data after each string). We can do both range checks together by checking "!eos" (we didn't find a NUL) and "eos == end" (it was on the last available byte, meaning there's nothing after). This replaces the existing "next > end" checks.

Note also that the decode_varint() calls have a similar problem (we don't even pass them "end"; they just keep parsing). These are probably OK in practice since varints have a finite length (we stop parsing when we'd overflow a uintmax_t), so the worst case is that we'd overflow into reading the trailing bytes of the index.

Signed-off-by: Jeff King <peff@peff.net>
---
 dir.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/dir.c b/dir.c
index f5293a6536..7b0513c476 100644
--- a/dir.c
+++ b/dir.c
@@ -2733,6 +2733,7 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
 {
 	struct untracked_cache_dir ud, *untracked;
 	const unsigned char *next, *data = rd->data, *end = rd->end;
+	const unsigned char *eos;
 	unsigned int value;
 	int i, len;
 
@@ -2756,21 +2757,24 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
 	ALLOC_ARRAY(ud.dirs, ud.dirs_nr);
 	data = next;
 
-	len = strlen((const char *)data);
-	next = data + len + 1;
-	if (next > rd->end)
+	eos = memchr(data, '\0', end - data);
+	if (!eos || eos == end)
 		return -1;
+	len = eos - data;
+	next = eos + 1;
+
 	*untracked_ = untracked = xmalloc(st_add3(sizeof(*untracked), len, 1));
 	memcpy(untracked, &ud, sizeof(ud));
 	memcpy(untracked->name, data, len + 1);
 	data = next;
 
 	for (i = 0; i < untracked->untracked_nr; i++) {
-		len = strlen((const char *)data);
-		next = data + len + 1;
-		if (next > rd->end)
+		eos = memchr(data, '\0', end - data);
+		if (!eos || eos == end)
 			return -1;
-		untracked->untracked[i] = xstrdup((const char*)data);
+		len = eos - data;
+		next = eos + 1;
+		untracked->untracked[i] = xmemdupz(data, len);
 		data = next;
 	}
 
-- 
2.21.0.1092.g8b0302e9c4
Previous: Jeff KingNext: Junio C Hamano
Message 6 of 13 in “Fix an off-by-one bug in the untracked cache code”
  1. 0/1 Fix an off-by-one bug in the untracked cache codeJohannes Schindelin via GitGitGadget, Apr 10, 2019
  2. 1/1 untracked cache: fix off-by-oneJohannes Schindelin via GitGitGadget, Apr 10, 2019
  3. Jeff KingApr 10, 2019
  4. Junio C HamanoApr 12, 2019
  5. 0/3 untracked cache parsing fixupsJeff King, Apr 18, 2019
  6. 1/3 untracked-cache: be defensive about missing NULs in indexJeff King, Apr 18, 2019
  7. Junio C HamanoApr 19, 2019
  8. 2/3 untracked-cache: simplify parsing by dropping "next"Jeff King, Apr 18, 2019
  9. Junio C HamanoApr 19, 2019
  10. 3/3 untracked-cache: simplify parsing by dropping "len"Jeff King, Apr 18, 2019
  11. 4/3 untracked-cache: use FLEX_ALLOC to create internal structsJeff King, Apr 18, 2019
  12. Duy NguyenApr 19, 2019
  13. Jeff KingApr 19, 2019

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.