line-log.c: prevent crash during union of too many ranges
authorAllan Xavier <allan.x.xavier@oracle.com>
Thu, 2 Mar 2017 17:29:02 +0000 (17:29 +0000)
committerJunio C Hamano <gitster@pobox.com>
Fri, 3 Mar 2017 19:16:20 +0000 (11:16 -0800)
The existing implementation of range_set_union does not correctly
reallocate memory, leading to a heap overflow when it attempts to union
more than 24 separate line ranges.

For struct range_set *out to grow correctly it must have out->nr set to
the current size of the buffer when it is passed to range_set_grow.
However, the existing implementation of range_set_union only updates
out->nr at the end of the function, meaning that it is always zero
before this. This results in range_set_grow never growing the buffer, as
well as some of the union logic itself being incorrect as !out->nr is
always true.

The reason why 24 is the limit is that the first allocation of size 1
ends up allocating a buffer of size 24 (due to the call to alloc_nr in
ALLOC_GROW). This goes some way to explain why this hasn't been
caught before.

Fix the problem by correctly updating out->nr after reallocating the
range_set. As this results in out->nr containing the same value as the
variable o, replace o with out->nr as well.

Finally, add a new test to help prevent the problem reoccurring in the
future. Thanks to Vegard Nossum for writing the test.

Signed-off-by: Allan Xavier <allan.x.xavier@oracle.com>
Reviewed-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
line-log.c
t/t4211-line-log.sh

index 916e724..47eaba7 100644 (file)
@@ -144,7 +144,7 @@ void sort_and_merge_range_set(struct range_set *rs)
 static void range_set_union(struct range_set *out,
                             struct range_set *a, struct range_set *b)
 {
-       int i = 0, j = 0, o = 0;
+       int i = 0, j = 0;
        struct range *ra = a->ranges;
        struct range *rb = b->ranges;
        /* cannot make an alias of out->ranges: it may change during grow */
@@ -167,16 +167,15 @@ static void range_set_union(struct range_set *out,
                        new = &rb[j++];
                if (new->start == new->end)
                        ; /* empty range */
-               else if (!o || out->ranges[o-1].end < new->start) {
+               else if (!out->nr || out->ranges[out->nr-1].end < new->start) {
                        range_set_grow(out, 1);
-                       out->ranges[o].start = new->start;
-                       out->ranges[o].end = new->end;
-                       o++;
-               } else if (out->ranges[o-1].end < new->end) {
-                       out->ranges[o-1].end = new->end;
+                       out->ranges[out->nr].start = new->start;
+                       out->ranges[out->nr].end = new->end;
+                       out->nr++;
+               } else if (out->ranges[out->nr-1].end < new->end) {
+                       out->ranges[out->nr-1].end = new->end;
                }
        }
-       out->nr = o;
 }
 
 /*
index 9d87777..d0377fa 100755 (executable)
@@ -106,4 +106,14 @@ test_expect_success '-L with --output' '
        test_line_count = 70 log
 '
 
+test_expect_success 'range_set_union' '
+       test_seq 500 > c.c &&
+       git add c.c &&
+       git commit -m "many lines" &&
+       test_seq 1000 > c.c &&
+       git add c.c &&
+       git commit -m "modify many lines" &&
+       git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c; done)
+'
+
 test_done