Skip to content

Commit 7db2587

Browse files
committed
delta: fix sign-extension of big left-shift
Our delta code was originally adapted from JGit, which itself adapted it from git itself. Due to this heritage, we inherited a bug from git.git in how we compute the delta offset, which was fixed upstream in 48fb7deb5 (Fix big left-shifts of unsigned char, 2009-06-17). As explained by Linus: Shifting 'unsigned char' or 'unsigned short' left can result in sign extension errors, since the C integer promotion rules means that the unsigned char/short will get implicitly promoted to a signed 'int' due to the shift (or due to other operations). This normally doesn't matter, but if you shift things up sufficiently, it will now set the sign bit in 'int', and a subsequent cast to a bigger type (eg 'long' or 'unsigned long') will now sign-extend the value despite the original expression being unsigned. One example of this would be something like unsigned long size; unsigned char c; size += c << 24; where despite all the variables being unsigned, 'c << 24' ends up being a signed entity, and will get sign-extended when then doing the addition in an 'unsigned long' type. Since git uses 'unsigned char' pointers extensively, we actually have this bug in a couple of places. In our delta code, we inherited such a bogus shift when computing the offset at which the delta base is to be found. Due to the sign extension we can end up with an offset where all the bits are set. This can allow an arbitrary memory read, as the addition in `base_len < off + len` can now overflow if `off` has all its bits set. Fix the issue by casting the result of `*delta++ << 24UL` to an unsigned integer again. Add a test with a crafted delta that would actually succeed with an out-of-bounds read in case where the cast wouldn't exist. Reported-by: Riccardo Schirone <rschiron@redhat.com> Test-provided-by: Riccardo Schirone <rschiron@redhat.com>
1 parent 967da2c commit 7db2587

File tree

3 files changed

+28
-17
lines changed

3 files changed

+28
-17
lines changed

src/delta.c

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,11 @@ int git_delta_apply(
539539
*out = NULL;
540540
*out_len = 0;
541541

542-
/* Check that the base size matches the data we were given;
543-
* if not we would underflow while accessing data from the
544-
* base object, resulting in data corruption or segfault.
545-
*/
542+
/*
543+
* Check that the base size matches the data we were given;
544+
* if not we would underflow while accessing data from the
545+
* base object, resulting in data corruption or segfault.
546+
*/
546547
if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) {
547548
giterr_set(GITERR_INVALID, "failed to apply delta: base size does not match given data");
548549
return -1;
@@ -564,42 +565,39 @@ int git_delta_apply(
564565
while (delta < delta_end) {
565566
unsigned char cmd = *delta++;
566567
if (cmd & 0x80) {
567-
/* cmd is a copy instruction; copy from the base.
568-
*/
568+
/* cmd is a copy instruction; copy from the base. */
569569
size_t off = 0, len = 0;
570570

571571
if (cmd & 0x01) off = *delta++;
572572
if (cmd & 0x02) off |= *delta++ << 8UL;
573573
if (cmd & 0x04) off |= *delta++ << 16UL;
574-
if (cmd & 0x08) off |= *delta++ << 24UL;
574+
if (cmd & 0x08) off |= ((unsigned) *delta++ << 24UL);
575575

576576
if (cmd & 0x10) len = *delta++;
577577
if (cmd & 0x20) len |= *delta++ << 8UL;
578578
if (cmd & 0x40) len |= *delta++ << 16UL;
579-
if (!len) len = 0x10000;
579+
if (!len) len = 0x10000;
580580

581581
if (base_len < off + len || res_sz < len)
582582
goto fail;
583583
memcpy(res_dp, base + off, len);
584584
res_dp += len;
585585
res_sz -= len;
586586

587-
}
588-
else if (cmd) {
589-
/* cmd is a literal insert instruction; copy from
590-
* the delta stream itself.
591-
*/
587+
} else if (cmd) {
588+
/*
589+
* cmd is a literal insert instruction; copy from
590+
* the delta stream itself.
591+
*/
592592
if (delta_end - delta < cmd || res_sz < cmd)
593593
goto fail;
594594
memcpy(res_dp, delta, cmd);
595595
delta += cmd;
596596
res_dp += cmd;
597597
res_sz -= cmd;
598598

599-
}
600-
else {
601-
/* cmd == 0 is reserved for future encodings.
602-
*/
599+
} else {
600+
/* cmd == 0 is reserved for future encodings. */
603601
goto fail;
604602
}
605603
}

tests/delta/apply.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include "clar_libgit2.h"
2+
3+
#include "delta.h"
4+
5+
void test_delta_apply__read_at_off(void)
6+
{
7+
unsigned char base[16] = { 0 }, delta[] = { 0x10, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00 };
8+
void *out;
9+
size_t outlen;
10+
11+
cl_git_fail(git_delta_apply(&out, &outlen, base, sizeof(base), delta, sizeof(delta)));
12+
}

tests/diff/binary.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "git2/sys/diff.h"
44

55
#include "buffer.h"
6+
#include "delta.h"
67
#include "filebuf.h"
78
#include "repository.h"
89

0 commit comments

Comments
 (0)