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

Re: [RFC PATCH 02/10] range-diff.c: don't use st_mult() for signed "int"

From
Jeff King <peff@peff.net>
Date
Dec 10, 2021, 03:39 UTC
Message-ID
<YbLL/YWbjc/sPRyH@coredump.intra.peff.net>
In-Reply-To
<RFC-patch-02.10-bd7d014c531-20211209T191653Z-avarab@gmail.com>
On Thu, Dec 09, 2021 at 08:19:19PM +0100, Ævar Arnfjörð Bjarmason wrote:
> As documented in 320d0b493a2 (add helpers for detecting size_t
> overflow, 2016-02-19) the arguments to st_mult() and st_add() "must be
> unsigned".

This isn't correct. The comment that says "must be unsigned" is for unsigned_mult_overflows(). Which indeed would not work correctly for a signed type. But st_add() is a separate function (and not a macro) because that means its arguments will always be promoted or converted into a size_t. And so no matter what you pass it, it will always itself pass a size_t into unsigned_mult_overflows().

Show 5 quoted lines
> In subsequent commits further overflows resulting in segfaults will be
> fixed in this code, but let's start by removing this supposed guard
> that does nothing except give us a false sense of
> security. E.g. providing an "n" of INT_MAX here will result in "1" on
> my system, causing us to write into memory.
This guard doesn't do nothing. Your patch here:
Show 8 quoted lines
> @@ -312,7 +312,7 @@ static void get_correspondences(struct string_list *a, struct string_list *b,
>  	int *cost, c, *a2b, *b2a;
>  	int i, j;
>  
> -	ALLOC_ARRAY(cost, st_mult(n, n));
> +	ALLOC_ARRAY(cost, n * n);
>  	ALLOC_ARRAY(a2b, n);
>  	ALLOC_ARRAY(b2a, n);

makes things strictly worse, because that "n * n" may overflow and cause us to under-allocate the array. You can see it in isolation with some extra code like this:

diff --git a/git.c b/git.c
index 5ff21be21f..63349e4b79 100644
--- a/git.c
+++ b/git.c
@@ -850,11 +850,23 @@ static int run_argv(int *argcp, const char ***argv)
 	return done_alias;
 }
 
+static void foo(void)
+{
+	int n = 2 << 16;
+
+	printf("n = %d\n", n);
+	printf("raw mult = %"PRIuMAX"\n", (uintmax_t)(n * n));
+	printf("st_mult = %"PRIuMAX"\n", (uintmax_t)st_mult(n, n));
+	exit(0);
+}
+
 int cmd_main(int argc, const char **argv)
 {
 	const char *cmd;
 	int done_help = 0;
 
+	foo();
+
 	cmd = argv[0];
 	if (!cmd)
 		cmd = "git-help";

With st_mult, we get the correct answer of 16GB. Without it, we end up
with 0!

Back to the original code, if you generate a test setup like this:

diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index e30bc48a29..f552d3086e 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -772,4 +772,11 @@ test_expect_success '--left-only/--right-only' '
 	test_cmp expect actual
 '
 
+test_expect_success 'giant case' '
+	test_commit base &&
+	test_commit_bulk --ref=refs/heads/v1 --message="v1 commit %s" 32768 &&
+	test_commit_bulk --ref=refs/heads/v2 --message="v2 commit %s" 32768 &&
+	git range-diff base v1 v2
+'
+
 test_done

Then we'd allocate a 0-length array for "cost" and segfault as soon as
we try to put anything in it. You can confirm this by applying your
patch, running under gdb, and stopping at the xmalloc() call inside
get_correspondences(). With st_mult(), then we come up with the correct
value of 16GB (or complain about overflow on a 32-bit system).

So st_add() is working as advertised here; it's goal is just to make
sure we never under-allocate. You are right, though, that the code after
that in get_correspondences() has trouble because of the signedness. If
the code used an "unsigned int", it would still be _wrong_. But when it
overflowed, it would always come up with a value under 4GB. So it might
produce a wrong answer, but it couldn't possibly point outside the
bounds of the array and cause a security problem.

But because it's a signed int, we overflow to a negative value and try
to look at memory far before the start of the array. So I can see how it
is tempting to argue that this st_mult() isn't really helping since we
segfault anyway. But I'd disagree. By correctly computing the value of
16GB instead of "0", we know that the ALLOC_ARRAY() line is doing the
right thing. And it may choose not to continue if it can't allocate
16GB. That may happen because you don't have the RAM, but also because
of GIT_ALLOC_LIMIT.

So if you set GIT_ALLOC_LIMIT=4g, for example, you are immune to the bug
here. We'd refuse the allocation and die there, which protects
downstream code from trying to fill in the array with bogus arithmetic.

Dropping the st_mult() does nothing to fix the actual problem (which is
that this function should use a more appropriate type), but introduces
new failure modes.

-Peff
Previous: Ævar Arnfjörð BjarmasonNext: Ævar Arnfjörð Bjarmason
Message 4 of 44 in “range-diff: fix segfault due to integer overflow”
  1. 00/10 range-diff: fix segfault due to integer overflowÆvar Arnfjörð Bjarmason, Dec 9, 2021
  2. 01/10 string-list API: change "nr" and "alloc" to "size_t"Ævar Arnfjörð Bjarmason, Dec 9, 2021
  3. 02/10 range-diff.c: don't use st_mult() for signed "int"Ævar Arnfjörð Bjarmason, Dec 9, 2021
  4. Jeff KingDec 10, 2021
  5. Ævar Arnfjörð BjarmasonDec 10, 2021
  6. Jeff KingDec 10, 2021
  7. Ævar Arnfjörð BjarmasonDec 10, 2021
  8. Phillip WoodDec 10, 2021
  9. Jeff KingDec 14, 2021
  10. Johannes SchindelinDec 10, 2021
  11. Ævar Arnfjörð BjarmasonDec 10, 2021
  12. Johannes SchindelinDec 11, 2021
  13. Ævar Arnfjörð BjarmasonDec 12, 2021
  14. Jeff KingDec 14, 2021
  15. 03/10 range-diff.c: use "size_t" to refer to "struct string_list"'s "nr"Ævar Arnfjörð Bjarmason, Dec 9, 2021
  16. 05/10 linear-assignment.c: split up compute_assignment() functionÆvar Arnfjörð Bjarmason, Dec 9, 2021
  17. 04/10 range-diff: zero out elements in "cost" firstÆvar Arnfjörð Bjarmason, Dec 9, 2021
  18. 06/10 linear-assignment.c: take "size_t", not "int" for *_countÆvar Arnfjörð Bjarmason, Dec 9, 2021
  19. 07/10 linear-assignment.c: convert a macro to a "static inline" functionÆvar Arnfjörð Bjarmason, Dec 9, 2021
  20. 08/10 linear-assignment.c: detect signed add/mul on GCC and ClangÆvar Arnfjörð Bjarmason, Dec 9, 2021
  21. Jeff KingDec 10, 2021
  22. 09/10 linear-assignment.c: add and use intprops.h from GnulibÆvar Arnfjörð Bjarmason, Dec 9, 2021
  23. 10/10 linear-assignment.c: use "intmax_t" instead of "int"Ævar Arnfjörð Bjarmason, Dec 9, 2021
  24. Jeff KingDec 10, 2021
  25. 0/5 range-diff: fix segfault due to integer overflowÆvar Arnfjörð Bjarmason, Dec 10, 2021
  26. 1/5 range-diff: zero out elements in "cost" firstÆvar Arnfjörð Bjarmason, Dec 10, 2021
  27. Jeff KingDec 14, 2021
  28. 2/5 linear-assignment.c: split up compute_assignment() functionÆvar Arnfjörð Bjarmason, Dec 10, 2021
  29. Jeff KingDec 14, 2021
  30. 3/5 linear-assignment.c: take "size_t", not "int" for *_countÆvar Arnfjörð Bjarmason, Dec 10, 2021
  31. Jeff KingDec 14, 2021
  32. 4/5 range-diff.c: rename "n" to "column_count" in get_correspondences()Ævar Arnfjörð Bjarmason, Dec 10, 2021
  33. Jeff KingDec 14, 2021
  34. 5/5 range-diff: fix integer overflow & segfault on cost[i + n * j]Ævar Arnfjörð Bjarmason, Dec 10, 2021
  35. Jeff KingDec 14, 2021
  36. Johannes SchindelinDec 10, 2021
  37. Ævar Arnfjörð BjarmasonDec 10, 2021
  38. Philip OakleyDec 21, 2021
  39. Ævar Arnfjörð BjarmasonDec 21, 2021
  40. Johannes SchindelinDec 22, 2021
  41. Jeff KingDec 22, 2021
  42. Philip OakleyDec 24, 2021
  43. Ævar Arnfjörð BjarmasonDec 24, 2021
  44. Philip OakleyDec 24, 2021

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.