Skip to content

Commit 07703ae

Browse files
committed
Merge branch 'jk/patch-corrupted-delta-fix'
Malformed or crafted data in packstream can make our code attempt to read or write past the allocated buffer and abort, instead of reporting an error, which has been fixed. * jk/patch-corrupted-delta-fix: t5303: use printf to generate delta bases patch-delta: handle truncated copy parameters patch-delta: consistently report corruption patch-delta: fix oob read t5303: test some corrupt deltas test-delta: read input into a heap buffer
2 parents 06880cf + 18f60f2 commit 07703ae

File tree

3 files changed

+111
-14
lines changed

3 files changed

+111
-14
lines changed

patch-delta.c

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,31 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
4040
cmd = *data++;
4141
if (cmd & 0x80) {
4242
unsigned long cp_off = 0, cp_size = 0;
43-
if (cmd & 0x01) cp_off = *data++;
44-
if (cmd & 0x02) cp_off |= (*data++ << 8);
45-
if (cmd & 0x04) cp_off |= (*data++ << 16);
46-
if (cmd & 0x08) cp_off |= ((unsigned) *data++ << 24);
47-
if (cmd & 0x10) cp_size = *data++;
48-
if (cmd & 0x20) cp_size |= (*data++ << 8);
49-
if (cmd & 0x40) cp_size |= (*data++ << 16);
43+
#define PARSE_CP_PARAM(bit, var, shift) do { \
44+
if (cmd & (bit)) { \
45+
if (data >= top) \
46+
goto bad_length; \
47+
var |= ((unsigned) *data++ << (shift)); \
48+
} } while (0)
49+
PARSE_CP_PARAM(0x01, cp_off, 0);
50+
PARSE_CP_PARAM(0x02, cp_off, 8);
51+
PARSE_CP_PARAM(0x04, cp_off, 16);
52+
PARSE_CP_PARAM(0x08, cp_off, 24);
53+
PARSE_CP_PARAM(0x10, cp_size, 0);
54+
PARSE_CP_PARAM(0x20, cp_size, 8);
55+
PARSE_CP_PARAM(0x40, cp_size, 16);
56+
#undef PARSE_CP_PARAM
5057
if (cp_size == 0) cp_size = 0x10000;
5158
if (unsigned_add_overflows(cp_off, cp_size) ||
5259
cp_off + cp_size > src_size ||
5360
cp_size > size)
54-
break;
61+
goto bad_length;
5562
memcpy(out, (char *) src_buf + cp_off, cp_size);
5663
out += cp_size;
5764
size -= cp_size;
5865
} else if (cmd) {
59-
if (cmd > size)
60-
break;
66+
if (cmd > size || cmd > top - data)
67+
goto bad_length;
6168
memcpy(out, data, cmd);
6269
out += cmd;
6370
data += cmd;
@@ -75,6 +82,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
7582

7683
/* sanity check */
7784
if (data != top || size != 0) {
85+
bad_length:
7886
error("delta replay has gone wild");
7987
bad:
8088
free(dst_buf);

t/helper/test-delta.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ int cmd__delta(int argc, const char **argv)
3434
return 1;
3535
}
3636
from_size = st.st_size;
37-
from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
38-
if (from_buf == MAP_FAILED) {
37+
from_buf = xmalloc(from_size);
38+
if (read_in_full(fd, from_buf, from_size) < 0) {
3939
perror(argv[2]);
4040
close(fd);
4141
return 1;
@@ -48,8 +48,8 @@ int cmd__delta(int argc, const char **argv)
4848
return 1;
4949
}
5050
data_size = st.st_size;
51-
data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
52-
if (data_buf == MAP_FAILED) {
51+
data_buf = xmalloc(data_size);
52+
if (read_in_full(fd, data_buf, data_size) < 0) {
5353
perror(argv[3]);
5454
close(fd);
5555
return 1;

t/t5303-pack-corruption-resilience.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,4 +311,93 @@ test_expect_success \
311311
test_must_fail git cat-file blob $blob_2 > /dev/null &&
312312
test_must_fail git cat-file blob $blob_3 > /dev/null'
313313

314+
# \0 - empty base
315+
# \1 - one byte in result
316+
# \1 - one literal byte (X)
317+
test_expect_success \
318+
'apply good minimal delta' \
319+
'printf "\0\1\1X" > minimal_delta &&
320+
test-tool delta -p /dev/null minimal_delta /dev/null'
321+
322+
# \0 - empty base
323+
# \1 - 1 byte in result
324+
# \2 - two literal bytes (one too many)
325+
test_expect_success \
326+
'apply delta with too many literal bytes' \
327+
'printf "\0\1\2XX" > too_big_literal &&
328+
test_must_fail test-tool delta -p /dev/null too_big_literal /dev/null'
329+
330+
# \4 - four bytes in base
331+
# \1 - one byte in result
332+
# \221 - copy, one byte offset, one byte size
333+
# \0 - copy from offset 0
334+
# \2 - copy two bytes (one too many)
335+
test_expect_success \
336+
'apply delta with too many copied bytes' \
337+
'printf "\4\1\221\0\2" > too_big_copy &&
338+
printf base >base &&
339+
test_must_fail test-tool delta -p base too_big_copy /dev/null'
340+
341+
# \0 - empty base
342+
# \2 - two bytes in result
343+
# \2 - two literal bytes (we are short one)
344+
test_expect_success \
345+
'apply delta with too few literal bytes' \
346+
'printf "\0\2\2X" > truncated_delta &&
347+
test_must_fail test-tool delta -p /dev/null truncated_delta /dev/null'
348+
349+
# \0 - empty base
350+
# \1 - one byte in result
351+
# \221 - copy, one byte offset, one byte size
352+
# \0 - copy from offset 0
353+
# \1 - copy one byte (we are short one)
354+
test_expect_success \
355+
'apply delta with too few bytes in base' \
356+
'printf "\0\1\221\0\1" > truncated_base &&
357+
test_must_fail test-tool delta -p /dev/null truncated_base /dev/null'
358+
359+
# \4 - four bytes in base
360+
# \2 - two bytes in result
361+
# \1 - one literal byte (X)
362+
# \221 - copy, one byte offset, one byte size
363+
# (offset/size missing)
364+
#
365+
# Note that the literal byte is necessary to get past the uninteresting minimum
366+
# delta size check.
367+
test_expect_success \
368+
'apply delta with truncated copy parameters' \
369+
'printf "\4\2\1X\221" > truncated_copy_delta &&
370+
printf base >base &&
371+
test_must_fail test-tool delta -p base truncated_copy_delta /dev/null'
372+
373+
# \0 - empty base
374+
# \1 - one byte in result
375+
# \1 - one literal byte (X)
376+
# \1 - trailing garbage command
377+
test_expect_success \
378+
'apply delta with trailing garbage literal' \
379+
'printf "\0\1\1X\1" > tail_garbage_literal &&
380+
test_must_fail test-tool delta -p /dev/null tail_garbage_literal /dev/null'
381+
382+
# \4 - four bytes in base
383+
# \1 - one byte in result
384+
# \1 - one literal byte (X)
385+
# \221 - copy, one byte offset, one byte size
386+
# \0 - copy from offset 0
387+
# \1 - copy 1 byte
388+
test_expect_success \
389+
'apply delta with trailing garbage copy' \
390+
'printf "\4\1\1X\221\0\1" > tail_garbage_copy &&
391+
printf base >base &&
392+
test_must_fail test-tool delta -p /dev/null tail_garbage_copy /dev/null'
393+
394+
# \0 - empty base
395+
# \1 - one byte in result
396+
# \1 - one literal byte (X)
397+
# \0 - bogus opcode
398+
test_expect_success \
399+
'apply delta with trailing garbage opcode' \
400+
'printf "\0\1\1X\0" > tail_garbage_opcode &&
401+
test_must_fail test-tool delta -p /dev/null tail_garbage_opcode /dev/null'
402+
314403
test_done

0 commit comments

Comments
 (0)