Subversion clients and servers, versions 1.6.0 - 1.6.3 and all
versions < 1.5.7, are vulnerable to several heap overflow problems
which may lead to remote code execution. The official advisory
(mirrored at http://subversion.tigris.org/security/CVE-2009-2411-advisory.txt)
follows:
Subversion clients and servers up to 1.6.3 (inclusive) have heap
overflow issues in the parsing of binary deltas.
Subversion clients and servers have multiple heap overflow issues in
the parsing of binary deltas. This is related to an allocation
vulnerability in the APR library used by Subversion.
Clients with commit access to a vulnerable server can cause a remote
heap overflow; servers can cause a heap overflow on vulnerable
clients that try to do a checkout or update.
This can lead to a DoS (an exploit has been tested) and to arbitrary
code execution (no exploit tested, but the possibility is clear).
Subversion clients and servers <= 1.5.6.
Subversion clients and servers 1.6.0 through 1.6.3 (inclusive).
Subversion 1.6.4
Subversion 1.5.7
(Search for "Patch" below to see the patches from 1.6.3 -> 1.6.4 and
1.5.6 -> 1.5.7. Search for "Recommendations" to get URLs for the
1.6.4 release and associated APR library patch.)
The libsvn_delta library does not contain sufficient input validation
of svndiff streams. If a stream with large windows is processed,
one of several integer overflows may lead to some boundary checks
incorrectly passing, which in turn can lead to a heap overflow.
A remote attacker with commit access to repository may be able to
execute code on a Subversion server. A malicious server may be able to
execute code on a Subversion client.
We recommend all users to upgrade to Subversion 1.6.4.
We recommend all users to upgrade to the latest versions of APR and
APR-UTIL, or apply the CVE-2009-2412 patch appropriate to their APR
installation from <http://www.apache.org/dist/apr/patches/>.
New Subversion packages can be found at:
http://subversion.tigris.org/project_packages.html
CVE-2009-2411 (Subversion)
CVE-2009-2412 (APR)
Matt Lewis, Google.
This patch applies to Subversion 1.6.x (apply with patch -p0 < patchfile):
— subversion/libsvn_delta/svndiff.c (revision 38519)
+++ subversion/libsvn_delta/svndiff.c (working copy)
@@ -60,10 +60,23 @@ struct encoder_baton {
apr_pool_t *pool;
};
+/* This is at least as big as the largest size of an integer that
/* Encode VAL into the buffer P using the variable-length svndiff
integer format. Return the incremented value of P after the
encoded bytes have been written. P must point to a buffer of size
at least MAX_ENCODED_INT_LEN.
This encoding uses the high bit of each byte as a continuation bit
and the other seven bits as data bits. High-order data bits are
@@ -85,7 +98,7 @@ encode_int(char *p, svn_filesize_t val)
svn_filesize_t v;
unsigned char cont;
SVN_ERR_ASSERT_NO_RETURN(val >= 0);
/* Figure out how many bytes we'll need. */
v = val >> 7;
@@ -96,6 +109,8 @@ encode_int(char *p, svn_filesize_t val)
n++;
}
SVN_ERR_ASSERT_NO_RETURN(n <= MAX_ENCODED_INT_LEN);
/* Encode the remaining bytes; n is always the number of bytes
coming after the one we're encoding. */
while (–n >= 0)
@@ -112,7 +127,7 @@ encode_int(char *p, svn_filesize_t val)
static void
append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
{
char buf[MAX_ENCODED_INT_LEN], *p;
p = encode_int(buf, val);
svn_stringbuf_appendbytes(header, buf, p - buf);
@@ -168,7 +183,7 @@ window_handler(svn_txdelta_window_t *window, void
svn_stringbuf_t *i1 = svn_stringbuf_create("", pool);
svn_stringbuf_t *header = svn_stringbuf_create("", pool);
const svn_string_t *newdata;
@@ -346,6 +361,8 @@ decode_file_offset(svn_filesize_t *val,
const unsigned char *p,
const unsigned char *end)
{
static svn_error_t *
-zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out)
+zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out, apr_size_t limit)
{
apr_size_t len;
char oldplace = in->data;
@@ -390,6 +409,13 @@ static svn_error_t *
/ First thing in the string is the original length. */
in->data = (char *)decode_size(&len, (unsigned char *)in->data,
(unsigned char *)in->data+in->len);
_("Decompression of svndiff data failed:
no size"));
_("Decompression of svndiff data failed: "
"size too large"));
/* We need to subtract the size of the encoded original length off the
still remaining input length. */
else if (op.length <= 0)
else if (op.length == 0)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
_("Invalid diff stream: insn %d has non-positive length"), n);
_("Invalid diff stream: insn %d has length zero"), n);
else if (op.length > tview_len - tpos)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
@@ -499,7 +525,8 @@ count_and_verify_instructions(int *ninst,
switch (op.action_code)
{
case svn_txdelta_source:
if (op.length > sview_len - op.offset)
if (op.length > sview_len - op.offset ||
op.offset > sview_len)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
_("Invalid diff stream: "
@@ -565,11 +592,11 @@ decode_window(svn_txdelta_window_t *window, svn_fi
instin = svn_stringbuf_ncreate((const char *)data, insend - data, pool);
instout = svn_stringbuf_create("", pool);
SVN_ERR(zlib_decode(instin, instout));
SVN_ERR(zlib_decode(instin, instout, MAX_INSTRUCTION_SECTION_LEN));
ndin = svn_stringbuf_ncreate((const char *)insend, newlen, pool);
ndout = svn_stringbuf_create("", pool);
SVN_ERR(zlib_decode(ndin, ndout));
SVN_ERR(zlib_decode(ndin, ndout, SVN_DELTA_WINDOW_SIZE));
newlen = ndout->len;
data = (unsigned char *)instout->data;
@@ -685,6 +712,14 @@ write_handler(void *baton,
if (p == NULL)
return SVN_NO_ERROR;
if (tview_len > SVN_DELTA_WINDOW_SIZE ||
sview_len > SVN_DELTA_WINDOW_SIZE ||
/* for svndiff1, newlen includes the original length */
newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
inslen > MAX_INSTRUCTION_SECTION_LEN)
return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
_("Svndiff contains a too-large window"));
/* Check for integer overflow. */
if (sview_offset < 0 || inslen + newlen < inslen
|| sview_len + tview_len < sview_len
@@ -841,6 +876,14 @@ read_window_header(svn_stream_t *stream, svn_files
SVN_ERR(read_one_size(inslen, stream));
SVN_ERR(read_one_size(newlen, stream));
*sview_len > SVN_DELTA_WINDOW_SIZE ||
/* for svndiff1, newlen includes the original length */
*newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
*inslen > MAX_INSTRUCTION_SECTION_LEN)
_("Svndiff contains a too-large window"));
/* Ensure that BUF has enough space for VIEW_LEN bytes. */
-static APR_INLINE void
+static APR_INLINE svn_error_t *
size_buffer(char **buf, apr_size_t *buf_size,
apr_size_t view_len, apr_pool_t *pool)
{
@@ -557,8 +557,11 @@ size_buffer(char **buf, apr_size_t *buf_size,
*buf_size *= 2;
if (*buf_size < view_len)
*buf_size = view_len;
SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
*buf = apr_palloc(pool, *buf_size);
}@@ -659,7 +662,7 @@ apply_window(svn_txdelta_window_t *window, void *b
>= ab->sbuf_offset + ab->sbuf_len)));
/* Make sure there's enough room in the target buffer. */
SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
/* Prepare the source buffer for reading from the input stream. */
if (window->sview_offset != ab->sbuf_offset
@@ -668,7 +671,8 @@ apply_window(svn_txdelta_window_t *window, void *b
char *old_sbuf = ab->sbuf;
/* Make sure there's enough room. */
size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len, ab->pool);
SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
ab->pool));
/* If the existing view overlaps with the new view, copy the
* overlap to the beginning of the new buffer. */
]]]
This patch applies to Subversion 1.5.x:
— subversion/libsvn_delta/svndiff.c (revision 38498)
+++ subversion/libsvn_delta/svndiff.c (working copy)
@@ -55,10 +55,23 @@ struct encoder_baton {
apr_pool_t *pool;
};
+/* This is at least as big as the largest size of an integer that
/* Encode VAL into the buffer P using the variable-length svndiff
integer format. Return the incremented value of P after the
encoded bytes have been written. P must point to a buffer of size
at least MAX_ENCODED_INT_LEN.
This encoding uses the high bit of each byte as a continuation bit
and the other seven bits as data bits. High-order data bits are
@@ -91,6 +104,8 @@ encode_int(char *p, svn_filesize_t val)
n++;
}
assert(n <= MAX_ENCODED_INT_LEN);
/* Encode the remaining bytes; n is always the number of bytes
coming after the one we're encoding. */
while (–n >= 0)
@@ -107,7 +122,7 @@ encode_int(char *p, svn_filesize_t val)
static void
append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
{
char buf[MAX_ENCODED_INT_LEN], *p;
p = encode_int(buf, val);
svn_stringbuf_appendbytes(header, buf, p - buf);
@@ -163,7 +178,7 @@ window_handler(svn_txdelta_window_t *window, void
svn_stringbuf_t *i1 = svn_stringbuf_create("", pool);
svn_stringbuf_t *header = svn_stringbuf_create("", pool);
const svn_string_t *newdata;
@@ -341,6 +356,8 @@ decode_file_offset(svn_filesize_t *val,
const unsigned char *p,
const unsigned char *end)
{
static svn_error_t *
-zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out)
+zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out, apr_size_t limit)
{
apr_size_t len;
char oldplace = in->data;
@@ -385,6 +404,13 @@ static svn_error_t *
/ First thing in the string is the original length. */
in->data = (char *)decode_size(&len, (unsigned char *)in->data,
(unsigned char *)in->data+in->len);
_("Decompression of svndiff data failed:
no size"));
_("Decompression of svndiff data failed: "
"size too large"));
/* We need to subtract the size of the encoded original length off the
still remaining input length. */
else if (op.length <= 0)
else if (op.length == 0)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
_("Invalid diff stream: insn %d has non-positive length"), n);
_("Invalid diff stream: insn %d has length zero"), n);
else if (op.length > tview_len - tpos)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
@@ -494,7 +520,8 @@ count_and_verify_instructions(int *ninst,
switch (op.action_code)
{
case svn_txdelta_source:
if (op.length > sview_len - op.offset)
if (op.length > sview_len - op.offset ||
op.offset > sview_len)
return svn_error_createf
(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
_("Invalid diff stream: "
@@ -560,11 +587,11 @@ decode_window(svn_txdelta_window_t *window, svn_fi
instin = svn_stringbuf_ncreate((const char *)data, insend - data, pool);
instout = svn_stringbuf_create("", pool);
SVN_ERR(zlib_decode(instin, instout));
SVN_ERR(zlib_decode(instin, instout, MAX_INSTRUCTION_SECTION_LEN));
ndin = svn_stringbuf_ncreate((const char *)insend, newlen, pool);
ndout = svn_stringbuf_create("", pool);
SVN_ERR(zlib_decode(ndin, ndout));
SVN_ERR(zlib_decode(ndin, ndout, SVN_DELTA_WINDOW_SIZE));
newlen = ndout->len;
data = (unsigned char *)instout->data;
@@ -680,6 +707,14 @@ write_handler(void *baton,
if (p == NULL)
return SVN_NO_ERROR;
if (tview_len > SVN_DELTA_WINDOW_SIZE ||
sview_len > SVN_DELTA_WINDOW_SIZE ||
/* for svndiff1, newlen includes the original length */
newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
inslen > MAX_INSTRUCTION_SECTION_LEN)
return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
_("Svndiff contains a too-large window"));
/* Check for integer overflow. */
if (sview_offset < 0 || inslen + newlen < inslen
|| sview_len + tview_len < sview_len
@@ -836,6 +871,14 @@ read_window_header(svn_stream_t *stream, svn_files
SVN_ERR(read_one_size(inslen, stream));
SVN_ERR(read_one_size(newlen, stream));
*sview_len > SVN_DELTA_WINDOW_SIZE ||
/* for svndiff1, newlen includes the original length */
*newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
*inslen > MAX_INSTRUCTION_SECTION_LEN)
_("Svndiff contains a too-large window"));
/* Ensure that BUF has enough space for VIEW_LEN bytes. */
-static APR_INLINE void
+static APR_INLINE svn_error_t *
size_buffer(char **buf, apr_size_t *buf_size,
apr_size_t view_len, apr_pool_t *pool)
{
@@ -507,8 +507,13 @@ size_buffer(char **buf, apr_size_t *buf_size,
*buf_size *= 2;
if (*buf_size < view_len)
*buf_size = view_len;
if (APR_ALIGN_DEFAULT(*buf_size) < *buf_size)
return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
"Diff stream resulted in invalid
buffer size.");
*buf = apr_palloc(pool, *buf_size);
}
+
@@ -609,7 +614,7 @@ apply_window(svn_txdelta_window_t *window, void *b
>= ab->sbuf_offset + ab->sbuf_len)));
/* Make sure there's enough room in the target buffer. */
SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
/* Prepare the source buffer for reading from the input stream. */
if (window->sview_offset != ab->sbuf_offset
@@ -618,7 +623,8 @@ apply_window(svn_txdelta_window_t *window, void *b
char *old_sbuf = ab->sbuf;
/* Make sure there's enough room. */
size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len, ab->pool);
SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
ab->pool));
/* If the existing view overlaps with the new view, copy the
* overlap to the beginning of the new buffer. */
]]]