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

[PATCH 06/13] dir.c: always copy input to add_pattern()

From
Jeff King <peff@peff.net>
Date
May 31, 2024, 11:31 UTC
Message-ID
<20240531113127.GF428814@coredump.intra.peff.net>
In-Reply-To
<20240531112433.GA428583@coredump.intra.peff.net>

The add_pattern() function has a subtle and undocumented gotcha: the pattern string you pass in must remain valid as long as the pattern_list is in use (and nor do we take ownership of it). This is easy to get wrong, causing either subtle bugs (because you free or reuse the string buffer) or leaks (because you copy the string, but don't track ownership separately).

All of this "pattern" code was originally the "exclude" mechanism. So this _usually_ works OK because you add entries in one of two ways:

  1. From the command-line (e.g., "--exclude"), in which case we're
     pointing to an argv entry which remains valid for the lifetime of
     the program.
  2. From a file (e.g., ".gitignore"), in which case we read the whole
     file into a buffer, attach it to the pattern_list's "filebuf"
     entry, then parse the buffer in-place (adding NULs). The strings
     point into the filebuf, which is cleaned up when the whole
     pattern_list goes away.

But other code, like sparse-checkout, reads individual lines from stdin and passes them one by one to add_pattern(), leaking each. We could fix this by refactoring it to take in the whole buffer at once, like (2) above, and stuff it in "filebuf". But given how subtle the interface is, let's just fix it to always copy the string.

That seems at first like we'd be wasting extra memory, but we can mitigate that:

  a. The path_pattern struct already uses a FLEXPTR, since we sometimes
     make a copy (when we see "foo/", we strip off the trailing slash,
     requiring a modifiable copy of the string).
     Since we'll now always embed the string inside the struct, we can
     switch to the regular FLEX_ARRAY pattern, saving us 8 bytes of
     pointer. So patterns with a trailing slash and ones under 8 bytes
     actually get smaller.
  b. Now that we don't need the original string to hang around, we can
     get rid of the "filebuf" mechanism entirely, and just free the file
     contents after parsing. Since files are the sources we'd expect to
     have the largest pattern sets, we should mostly break even on
     stuffing the same data into the individual structs.

This patch just adjusts the add_pattern() interface; it doesn't fix any leaky callers yet.

Signed-off-by: Jeff King <peff@peff.net>
---
 dir.c | 15 +++++----------
 dir.h |  3 ++-
 2 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/dir.c b/dir.c
index d812d521b0..8308d167c8 100644
--- a/dir.c
+++ b/dir.c
@@ -925,12 +925,7 @@ void add_pattern(const char *string, const char *base,
 	int nowildcardlen;
 
 	parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen);
-	if (flags & PATTERN_FLAG_MUSTBEDIR) {
-		FLEXPTR_ALLOC_MEM(pattern, pattern, string, patternlen);
-	} else {
-		pattern = xmalloc(sizeof(*pattern));
-		pattern->pattern = string;
-	}
+	FLEX_ALLOC_MEM(pattern, pattern, string, patternlen);
 	pattern->patternlen = patternlen;
 	pattern->nowildcardlen = nowildcardlen;
 	pattern->base = base;
@@ -972,7 +967,6 @@ void clear_pattern_list(struct pattern_list *pl)
 	for (i = 0; i < pl->nr; i++)
 		free(pl->patterns[i]);
 	free(pl->patterns);
-	free(pl->filebuf);
 	clear_pattern_entry_hashmap(&pl->recursive_hashmap);
 	clear_pattern_entry_hashmap(&pl->parent_hashmap);
 
@@ -1166,23 +1160,23 @@ static int add_patterns(const char *fname, const char *base, int baselen,
 	}
 
 	add_patterns_from_buffer(buf, size, base, baselen, pl);
+	free(buf);
 	return 0;
 }
 
 static int add_patterns_from_buffer(char *buf, size_t size,
 				    const char *base, int baselen,
 				    struct pattern_list *pl)
 {
+	char *orig = buf;
 	int i, lineno = 1;
 	char *entry;
 
 	hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
 	hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
 
-	pl->filebuf = buf;
-
 	if (skip_utf8_bom(&buf, size))
-		size -= buf - pl->filebuf;
+		size -= buf - orig;
 
 	entry = buf;
 
@@ -1222,6 +1216,7 @@ int add_patterns_from_blob_to_list(
 		return r;
 
 	add_patterns_from_buffer(buf, size, base, baselen, pl);
+	free(buf);
 	return 0;
 }
 
diff --git a/dir.h b/dir.h
index b9e8e96128..c8ff308fae 100644
--- a/dir.h
+++ b/dir.h
@@ -62,7 +62,6 @@ struct path_pattern {
 	 */
 	struct pattern_list *pl;
 
-	const char *pattern;
 	int patternlen;
 	int nowildcardlen;
 	const char *base;
@@ -74,6 +73,8 @@ struct path_pattern {
 	 * and from -1 decrementing for patterns from CLI args.
 	 */
 	int srcpos;
+
+	char pattern[FLEX_ARRAY];
 };
 
 /* used for hashmaps for cone patterns */
-- 
2.45.1.727.ge984192922
Previous: Jeff KingNext: Junio C Hamano
Message 9 of 43 in “leak fixes for sparse-checkout code”
  1. 0/13 leak fixes for sparse-checkout codeJeff King, May 31, 2024
  2. 01/13 sparse-checkout: free string list in write_cone_to_file()Jeff King, May 31, 2024
  3. 02/13 sparse-checkout: pass string literals directly to add_pattern()Jeff King, May 31, 2024
  4. Junio C HamanoMay 31, 2024
  5. Jeff KingJun 4, 2024
  6. 03/13 dir.c: free strings in sparse cone pattern hashmapsJeff King, May 31, 2024
  7. 04/13 sparse-checkout: clear patterns when init() sees existing sparse fileJeff King, May 31, 2024
  8. 05/13 dir.c: free removed sparse-pattern hashmap entriesJeff King, May 31, 2024
  9. 06/13 dir.c: always copy input to add_pattern()Jeff King, May 31, 2024
  10. Junio C HamanoMay 31, 2024
  11. 07/13 sparse-checkout: reuse --stdin buffer when reading patternsJeff King, May 31, 2024
  12. 08/13 sparse-checkout: always free "line" strbuf after reading inputJeff King, May 31, 2024
  13. 09/13 sparse-checkout: refactor temporary sparse_checkout_patternsJeff King, May 31, 2024
  14. Patrick SteinhardtJun 4, 2024
  15. Jeff KingJun 4, 2024
  16. 10/13 sparse-checkout: free sparse_filename after useJeff King, May 31, 2024
  17. Patrick SteinhardtJun 4, 2024
  18. Jeff KingJun 4, 2024
  19. Patrick SteinhardtJun 4, 2024
  20. 11/13 sparse-checkout: free pattern list in sparse_checkout_list()Jeff King, May 31, 2024
  21. 12/13 sparse-checkout: free string list after displayingJeff King, May 31, 2024
  22. 13/13 sparse-checkout: free duplicate hashmap entriesJeff King, May 31, 2024
  23. Patrick SteinhardtJun 4, 2024
  24. Jeff KingMay 31, 2024
  25. Junio C HamanoMay 31, 2024
  26. 0/13 leak fixes for sparse-checkout codeJeff King, Jun 4, 2024
  27. 01/13 sparse-checkout: free string list in write_cone_to_file()Jeff King, Jun 4, 2024
  28. 02/13 sparse-checkout: pass string literals directly to add_pattern()Jeff King, Jun 4, 2024
  29. 03/13 dir.c: free strings in sparse cone pattern hashmapsJeff King, Jun 4, 2024
  30. 04/13 sparse-checkout: clear patterns when init() sees existing sparse fileJeff King, Jun 4, 2024
  31. 05/13 dir.c: free removed sparse-pattern hashmap entriesJeff King, Jun 4, 2024
  32. 06/13 dir.c: always copy input to add_pattern()Jeff King, Jun 4, 2024
  33. René ScharfeJun 5, 2024
  34. Jeff KingJun 5, 2024
  35. Junio C HamanoJun 5, 2024
  36. 07/13 sparse-checkout: reuse --stdin buffer when reading patternsJeff King, Jun 4, 2024
  37. 08/13 sparse-checkout: always free "line" strbuf after reading inputJeff King, Jun 4, 2024
  38. 09/13 sparse-checkout: refactor temporary sparse_checkout_patternsJeff King, Jun 4, 2024
  39. 10/13 sparse-checkout: free sparse_filename after useJeff King, Jun 4, 2024
  40. 11/13 sparse-checkout: free pattern list in sparse_checkout_list()Jeff King, Jun 4, 2024
  41. 12/13 sparse-checkout: free string list after displayingJeff King, Jun 4, 2024
  42. 13/13 sparse-checkout: free duplicate hashmap entriesJeff King, Jun 4, 2024
  43. Patrick SteinhardtJun 4, 2024

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.