mirror of
https://github.com/git/git
synced 2024-10-28 19:25:47 +00:00
9514b0b226
When we see a delta command instructing us to copy bytes from the base, we have to read the offset and size from the delta stream. We do this without checking whether we're at the end of the stream, meaning we may read past the end of the buffer. In practice this isn't exploitable in any interesting way because: 1. Deltas are always in packfiles, so we have at least a 20-byte trailer that we'll end up reading. 2. The worst case is that we try to perform a nonsense copy from the base object into the result, based on whatever was in the pack stream next. In most cases this will simply fail due to our bounds-checks against the base or the result. But even if you carefully constructed a pack stream for which it succeeds, it wouldn't perform any delta operation that you couldn't have simply included in a non-broken form. But obviously it's poor form to read past the end of the buffer we've been given. Unfortunately there's no easy way to do a single length check, since the number of bytes we need depends on the number of bits set in the initial command byte. So we'll just check each byte as we parse. We can hide the complexity in a macro; it's ugly, but not as ugly as writing out each individual conditional. Signed-off-by: Jeff King <peff@peff.net> Reviewed-by: Nicolas Pitre <nico@fluxnic.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
94 lines
2.4 KiB
C
94 lines
2.4 KiB
C
/*
|
|
* patch-delta.c:
|
|
* recreate a buffer from a source and the delta produced by diff-delta.c
|
|
*
|
|
* (C) 2005 Nicolas Pitre <nico@fluxnic.net>
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "git-compat-util.h"
|
|
#include "delta.h"
|
|
|
|
void *patch_delta(const void *src_buf, unsigned long src_size,
|
|
const void *delta_buf, unsigned long delta_size,
|
|
unsigned long *dst_size)
|
|
{
|
|
const unsigned char *data, *top;
|
|
unsigned char *dst_buf, *out, cmd;
|
|
unsigned long size;
|
|
|
|
if (delta_size < DELTA_SIZE_MIN)
|
|
return NULL;
|
|
|
|
data = delta_buf;
|
|
top = (const unsigned char *) delta_buf + delta_size;
|
|
|
|
/* make sure the orig file size matches what we expect */
|
|
size = get_delta_hdr_size(&data, top);
|
|
if (size != src_size)
|
|
return NULL;
|
|
|
|
/* now the result size */
|
|
size = get_delta_hdr_size(&data, top);
|
|
dst_buf = xmallocz(size);
|
|
|
|
out = dst_buf;
|
|
while (data < top) {
|
|
cmd = *data++;
|
|
if (cmd & 0x80) {
|
|
unsigned long cp_off = 0, cp_size = 0;
|
|
#define PARSE_CP_PARAM(bit, var, shift) do { \
|
|
if (cmd & (bit)) { \
|
|
if (data >= top) \
|
|
goto bad_length; \
|
|
var |= ((unsigned) *data++ << (shift)); \
|
|
} } while (0)
|
|
PARSE_CP_PARAM(0x01, cp_off, 0);
|
|
PARSE_CP_PARAM(0x02, cp_off, 8);
|
|
PARSE_CP_PARAM(0x04, cp_off, 16);
|
|
PARSE_CP_PARAM(0x08, cp_off, 24);
|
|
PARSE_CP_PARAM(0x10, cp_size, 0);
|
|
PARSE_CP_PARAM(0x20, cp_size, 8);
|
|
PARSE_CP_PARAM(0x40, cp_size, 16);
|
|
#undef PARSE_CP_PARAM
|
|
if (cp_size == 0) cp_size = 0x10000;
|
|
if (unsigned_add_overflows(cp_off, cp_size) ||
|
|
cp_off + cp_size > src_size ||
|
|
cp_size > size)
|
|
goto bad_length;
|
|
memcpy(out, (char *) src_buf + cp_off, cp_size);
|
|
out += cp_size;
|
|
size -= cp_size;
|
|
} else if (cmd) {
|
|
if (cmd > size || cmd > top - data)
|
|
goto bad_length;
|
|
memcpy(out, data, cmd);
|
|
out += cmd;
|
|
data += cmd;
|
|
size -= cmd;
|
|
} else {
|
|
/*
|
|
* cmd == 0 is reserved for future encoding
|
|
* extensions. In the mean time we must fail when
|
|
* encountering them (might be data corruption).
|
|
*/
|
|
error("unexpected delta opcode 0");
|
|
goto bad;
|
|
}
|
|
}
|
|
|
|
/* sanity check */
|
|
if (data != top || size != 0) {
|
|
bad_length:
|
|
error("delta replay has gone wild");
|
|
bad:
|
|
free(dst_buf);
|
|
return NULL;
|
|
}
|
|
|
|
*dst_size = out - dst_buf;
|
|
return dst_buf;
|
|
}
|