Skip to content

Commit 74d156f

Browse files
phillipwoodgitster
authored andcommitted
diff --color-moved-ws: fix double free crash
Running git diff --color-moved-ws=allow-indentation-change v2.18.0 v2.19.0 results in a crash due to a double free. This happens when two potential moved blocks start with consecutive lines. As pmb_advance_or_null_multi_match() advances it copies the ws_delta from the last matching line to the next. When the first of our consecutive lines is advanced its ws_delta well be copied to the second, overwriting the ws_delta of the block containing the second line. Then when the second line is advanced it will copy the new ws_delta to the line below it and so on. Eventually one of these blocks will stop matching and the ws_delta will be freed. From then on the other block is in a use-after-free state and when it stops matching it will try to free the ws_delta that has already been freed by the other block. The solution is to store the ws_delta in the array of potential moved blocks rather than with the lines. This means that it no longer needs to be copied around and one block cannot overwrite the ws_delta of another. Additionally it saves some malloc/free calls as we don't keep allocating and freeing ws_deltas. Signed-off-by: Phillip Wood <[email protected]> Reviewed-by: Stefan Beller <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent fe8321e commit 74d156f

File tree

1 file changed

+45
-37
lines changed

1 file changed

+45
-37
lines changed

diff.c

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,6 @@ struct moved_entry {
776776
struct hashmap_entry ent;
777777
const struct emitted_diff_symbol *es;
778778
struct moved_entry *next_line;
779-
struct ws_delta *wsd;
780779
};
781780

782781
/**
@@ -793,6 +792,17 @@ struct ws_delta {
793792
};
794793
#define WS_DELTA_INIT { NULL, 0 }
795794

795+
struct moved_block {
796+
struct moved_entry *match;
797+
struct ws_delta wsd;
798+
};
799+
800+
static void moved_block_clear(struct moved_block *b)
801+
{
802+
FREE_AND_NULL(b->wsd.string);
803+
b->match = NULL;
804+
}
805+
796806
static int compute_ws_delta(const struct emitted_diff_symbol *a,
797807
const struct emitted_diff_symbol *b,
798808
struct ws_delta *out)
@@ -810,7 +820,7 @@ static int compute_ws_delta(const struct emitted_diff_symbol *a,
810820
static int cmp_in_block_with_wsd(const struct diff_options *o,
811821
const struct moved_entry *cur,
812822
const struct moved_entry *match,
813-
struct moved_entry *pmb,
823+
struct moved_block *pmb,
814824
int n)
815825
{
816826
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
@@ -830,25 +840,24 @@ static int cmp_in_block_with_wsd(const struct diff_options *o,
830840
if (strcmp(a, b))
831841
return 1;
832842

833-
if (!pmb->wsd)
843+
if (!pmb->wsd.string)
834844
/*
835-
* No white space delta was carried forward? This can happen
836-
* when we exit early in this function and do not carry
837-
* forward ws.
845+
* The white space delta is not active? This can happen
846+
* when we exit early in this function.
838847
*/
839848
return 1;
840849

841850
/*
842-
* The indent changes of the block are known and carried forward in
851+
* The indent changes of the block are known and stored in
843852
* pmb->wsd; however we need to check if the indent changes of the
844853
* current line are still the same as before.
845854
*
846855
* To do so we need to compare 'l' to 'cur', adjusting the
847856
* one of them for the white spaces, depending which was longer.
848857
*/
849858

850-
wslen = strlen(pmb->wsd->string);
851-
if (pmb->wsd->current_longer) {
859+
wslen = strlen(pmb->wsd.string);
860+
if (pmb->wsd.current_longer) {
852861
c += wslen;
853862
cl -= wslen;
854863
} else {
@@ -898,7 +907,6 @@ static struct moved_entry *prepare_entry(struct diff_options *o,
898907
ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
899908
ret->es = l;
900909
ret->next_line = NULL;
901-
ret->wsd = NULL;
902910

903911
return ret;
904912
}
@@ -938,76 +946,72 @@ static void add_lines_to_move_detection(struct diff_options *o,
938946
static void pmb_advance_or_null(struct diff_options *o,
939947
struct moved_entry *match,
940948
struct hashmap *hm,
941-
struct moved_entry **pmb,
949+
struct moved_block *pmb,
942950
int pmb_nr)
943951
{
944952
int i;
945953
for (i = 0; i < pmb_nr; i++) {
946-
struct moved_entry *prev = pmb[i];
954+
struct moved_entry *prev = pmb[i].match;
947955
struct moved_entry *cur = (prev && prev->next_line) ?
948956
prev->next_line : NULL;
949957
if (cur && !hm->cmpfn(o, cur, match, NULL)) {
950-
pmb[i] = cur;
958+
pmb[i].match = cur;
951959
} else {
952-
pmb[i] = NULL;
960+
pmb[i].match = NULL;
953961
}
954962
}
955963
}
956964

957965
static void pmb_advance_or_null_multi_match(struct diff_options *o,
958966
struct moved_entry *match,
959967
struct hashmap *hm,
960-
struct moved_entry **pmb,
968+
struct moved_block *pmb,
961969
int pmb_nr, int n)
962970
{
963971
int i;
964972
char *got_match = xcalloc(1, pmb_nr);
965973

966974
for (; match; match = hashmap_get_next(hm, match)) {
967975
for (i = 0; i < pmb_nr; i++) {
968-
struct moved_entry *prev = pmb[i];
976+
struct moved_entry *prev = pmb[i].match;
969977
struct moved_entry *cur = (prev && prev->next_line) ?
970978
prev->next_line : NULL;
971979
if (!cur)
972980
continue;
973-
if (!cmp_in_block_with_wsd(o, cur, match, pmb[i], n))
981+
if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n))
974982
got_match[i] |= 1;
975983
}
976984
}
977985

978986
for (i = 0; i < pmb_nr; i++) {
979987
if (got_match[i]) {
980-
/* Carry the white space delta forward */
981-
pmb[i]->next_line->wsd = pmb[i]->wsd;
982-
pmb[i] = pmb[i]->next_line;
988+
/* Advance to the next line */
989+
pmb[i].match = pmb[i].match->next_line;
983990
} else {
984-
if (pmb[i]->wsd) {
985-
free(pmb[i]->wsd->string);
986-
FREE_AND_NULL(pmb[i]->wsd);
987-
}
988-
pmb[i] = NULL;
991+
moved_block_clear(&pmb[i]);
989992
}
990993
}
991994
}
992995

993-
static int shrink_potential_moved_blocks(struct moved_entry **pmb,
996+
static int shrink_potential_moved_blocks(struct moved_block *pmb,
994997
int pmb_nr)
995998
{
996999
int lp, rp;
9971000

9981001
/* Shrink the set of potential block to the remaining running */
9991002
for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
1000-
while (lp < pmb_nr && pmb[lp])
1003+
while (lp < pmb_nr && pmb[lp].match)
10011004
lp++;
10021005
/* lp points at the first NULL now */
10031006

1004-
while (rp > -1 && !pmb[rp])
1007+
while (rp > -1 && !pmb[rp].match)
10051008
rp--;
10061009
/* rp points at the last non-NULL */
10071010

10081011
if (lp < pmb_nr && rp > -1 && lp < rp) {
10091012
pmb[lp] = pmb[rp];
1010-
pmb[rp] = NULL;
1013+
pmb[rp].match = NULL;
1014+
pmb[rp].wsd.string = NULL;
10111015
rp--;
10121016
lp++;
10131017
}
@@ -1054,7 +1058,7 @@ static void mark_color_as_moved(struct diff_options *o,
10541058
struct hashmap *add_lines,
10551059
struct hashmap *del_lines)
10561060
{
1057-
struct moved_entry **pmb = NULL; /* potentially moved blocks */
1061+
struct moved_block *pmb = NULL; /* potentially moved blocks */
10581062
int pmb_nr = 0, pmb_alloc = 0;
10591063
int n, flipped_block = 1, block_length = 0;
10601064

@@ -1083,7 +1087,11 @@ static void mark_color_as_moved(struct diff_options *o,
10831087
}
10841088

10851089
if (!match) {
1090+
int i;
1091+
10861092
adjust_last_block(o, n, block_length);
1093+
for(i = 0; i < pmb_nr; i++)
1094+
moved_block_clear(&pmb[i]);
10871095
pmb_nr = 0;
10881096
block_length = 0;
10891097
continue;
@@ -1111,14 +1119,12 @@ static void mark_color_as_moved(struct diff_options *o,
11111119
ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
11121120
if (o->color_moved_ws_handling &
11131121
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
1114-
struct ws_delta *wsd = xmalloc(sizeof(*match->wsd));
1115-
if (compute_ws_delta(l, match->es, wsd)) {
1116-
match->wsd = wsd;
1117-
pmb[pmb_nr++] = match;
1118-
} else
1119-
free(wsd);
1122+
if (compute_ws_delta(l, match->es,
1123+
&pmb[pmb_nr].wsd))
1124+
pmb[pmb_nr++].match = match;
11201125
} else {
1121-
pmb[pmb_nr++] = match;
1126+
pmb[pmb_nr].wsd.string = NULL;
1127+
pmb[pmb_nr++].match = match;
11221128
}
11231129
}
11241130

@@ -1135,6 +1141,8 @@ static void mark_color_as_moved(struct diff_options *o,
11351141
}
11361142
adjust_last_block(o, n, block_length);
11371143

1144+
for(n = 0; n < pmb_nr; n++)
1145+
moved_block_clear(&pmb[n]);
11381146
free(pmb);
11391147
}
11401148

0 commit comments

Comments
 (0)