Merge branch 'bug20894_029_v3'

This commit is contained in:
Nick Mathewson 2017-02-14 19:10:20 -05:00
commit 7e469c1002
7 changed files with 132 additions and 13 deletions

9
changes/bug20894 Normal file
View File

@ -0,0 +1,9 @@
o Major bugfixes (HTTP, parsing):
- When parsing a malformed content-length field from an HTTP message,
do not read off the end of the buffer. This bug was a potential
remote denial-of-service attack against Tor clients and relays.
A workaround was released in October 2016, which prevents this
bug from crashing Tor. This is a fix for the underlying issue,
which should no longer matter (if you applied the earlier patch).
Fixes bug 20894; bugfix on 0.2.0.16-alpha. Bug found by fuzzing
using AFL (http://lcamtuf.coredump.cx/afl/).

View File

@ -704,6 +704,19 @@ tor_strisnonupper(const char *s)
return 1;
}
/** Return true iff every character in <b>s</b> is whitespace space; else
* return false. */
int
tor_strisspace(const char *s)
{
while (*s) {
if (!TOR_ISSPACE(*s))
return 0;
s++;
}
return 1;
}
/** As strcmp, except that either string may be NULL. The NULL string is
* considered to be before any non-NULL string. */
int

View File

@ -186,6 +186,7 @@ void tor_strlower(char *s) ATTR_NONNULL((1));
void tor_strupper(char *s) ATTR_NONNULL((1));
int tor_strisprint(const char *s) ATTR_NONNULL((1));
int tor_strisnonupper(const char *s) ATTR_NONNULL((1));
int tor_strisspace(const char *s);
int strcmp_opt(const char *s1, const char *s2);
int strcmpstart(const char *s1, const char *s2) ATTR_NONNULL((1,2));
int strcmp_len(const char *s1, const char *s2, size_t len) ATTR_NONNULL((1,2));

View File

@ -1138,6 +1138,52 @@ buf_find_string_offset(const buf_t *buf, const char *s, size_t n)
return -1;
}
/**
* Scan the HTTP headers in the <b>headerlen</b>-byte memory range at
* <b>headers</b>, looking for a "Content-Length" header. Try to set
* *<b>result_out</b> to the numeric value of that header if possible.
* Return -1 if the header was malformed, 0 if it was missing, and 1 if
* it was present and well-formed.
*/
STATIC int
buf_http_find_content_length(const char *headers, size_t headerlen,
size_t *result_out)
{
const char *p, *newline;
char *len_str, *eos=NULL;
size_t remaining, result;
int ok;
*result_out = 0; /* The caller shouldn't look at this unless the
* return value is 1, but let's prevent confusion */
#define CONTENT_LENGTH "\r\nContent-Length: "
p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
if (p == NULL)
return 0;
tor_assert(p >= headers && p < headers+headerlen);
remaining = (headers+headerlen)-p;
p += strlen(CONTENT_LENGTH);
remaining -= strlen(CONTENT_LENGTH);
newline = memchr(p, '\n', remaining);
if (newline == NULL)
return -1;
len_str = tor_memdup_nulterm(p, newline-p);
/* We limit the size to INT_MAX because other parts of the buffer.c
* code don't like buffers to be any bigger than that. */
result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos);
if (eos && !tor_strisspace(eos)) {
ok = 0;
} else {
*result_out = result;
}
tor_free(len_str);
return ok ? 1 : -1;
}
/** There is a (possibly incomplete) http statement on <b>buf</b>, of the
* form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.)
* If a) the headers include a Content-Length field and all bytes in
@ -1163,9 +1209,10 @@ fetch_from_buf_http(buf_t *buf,
char **body_out, size_t *body_used, size_t max_bodylen,
int force_complete)
{
char *headers, *p;
size_t headerlen, bodylen, contentlen;
char *headers;
size_t headerlen, bodylen, contentlen=0;
int crlf_offset;
int r;
check();
if (!buf->head)
@ -1201,17 +1248,12 @@ fetch_from_buf_http(buf_t *buf,
return -1;
}
#define CONTENT_LENGTH "\r\nContent-Length: "
p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
if (p) {
int i;
i = atoi(p+strlen(CONTENT_LENGTH));
if (i < 0) {
log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like "
"someone is trying to crash us.");
return -1;
}
contentlen = i;
r = buf_http_find_content_length(headers, headerlen, &contentlen);
if (r == -1) {
log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe "
"someone is trying to crash us.");
return -1;
} else if (r == 1) {
/* if content-length is malformed, then our body length is 0. fine. */
log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen);
if (bodylen < contentlen) {
@ -1224,7 +1266,11 @@ fetch_from_buf_http(buf_t *buf,
bodylen = contentlen;
log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen);
}
} else {
tor_assert(r == 0);
/* Leave bodylen alone */
}
/* all happy. copy into the appropriate places, and return 1 */
if (headers_out) {
*headers_out = tor_malloc(headerlen+1);

View File

@ -100,5 +100,10 @@ struct buf_t {
};
#endif
#ifdef BUFFERS_PRIVATE
STATIC int buf_http_find_content_length(const char *headers, size_t headerlen,
size_t *result_out);
#endif
#endif

View File

@ -765,6 +765,49 @@ test_buffers_chunk_size(void *arg)
;
}
static void
test_buffers_find_contentlen(void *arg)
{
static const struct {
const char *headers;
int r;
int contentlen;
} results[] = {
{ "Blah blah\r\nContent-Length: 1\r\n\r\n", 1, 1 },
{ "Blah blah\r\n\r\n", 0, 0 }, /* no content-len */
{ "Blah blah Content-Length: 1\r\n", 0, 0 }, /* no content-len. */
{ "Blah blah\r\nContent-Length: 100000\r\n", 1, 100000},
{ "Blah blah\r\nContent-Length: 1000000000000000000000000\r\n", -1, 0},
{ "Blah blah\r\nContent-Length: 0\r\n", 1, 0},
{ "Blah blah\r\nContent-Length: -1\r\n", -1, 0},
{ "Blah blah\r\nContent-Length: 1x\r\n", -1, 0},
{ "Blah blah\r\nContent-Length: 1 x\r\n", -1, 0},
{ "Blah blah\r\nContent-Length: 1 \r\n", 1, 1},
{ "Blah blah\r\nContent-Length: \r\n", -1, 0},
{ "Blah blah\r\nContent-Length: ", -1, 0},
{ "Blah blah\r\nContent-Length: 5050", -1, 0},
{ NULL, 0, 0 }
};
int i;
(void)arg;
for (i = 0; results[i].headers; ++i) {
int r;
size_t sz;
size_t headerlen = strlen(results[i].headers);
char * tmp = tor_memdup(results[i].headers, headerlen);/* ensure no eos */
sz = 999; /* to ensure it gets set */
r = buf_http_find_content_length(tmp, headerlen, &sz);
tor_free(tmp);
log_debug(LD_DIR, "%d: %s", i, escaped(results[i].headers));
tt_int_op(r, ==, results[i].r);
tt_int_op(sz, ==, results[i].contentlen);
}
done:
;
}
struct testcase_t buffer_tests[] = {
{ "basic", test_buffers_basic, TT_FORK, NULL, NULL },
{ "copy", test_buffer_copy, TT_FORK, NULL, NULL },
@ -780,6 +823,7 @@ struct testcase_t buffer_tests[] = {
{ "tls_read_mocked", test_buffers_tls_read_mocked, 0,
NULL, NULL },
{ "chunk_size", test_buffers_chunk_size, 0, NULL, NULL },
{ "find_contentlen", test_buffers_find_contentlen, 0, NULL, NULL },
END_OF_TESTCASES
};

View File

@ -9,6 +9,7 @@
#define CONTROL_PRIVATE
#define UTIL_PRIVATE
#include "or.h"
#include "buffers.h"
#include "config.h"
#include "control.h"
#include "test.h"