fixed crash on invalid input in http_parser (#782)

fixed crash on invalid input to http_parser
This commit is contained in:
Arvid Norberg 2016-06-04 09:53:23 -04:00
parent ec182492cc
commit 3624ce6cfd
4 changed files with 151 additions and 24 deletions

View File

@ -1,5 +1,6 @@
1.1.1 release 1.1.1 release
* fixed crash on invalid input in http_parser
* upgraded to libtommath 1.0 * upgraded to libtommath 1.0
* fixed parsing of IPv6 endpoint with invalid port character separator * fixed parsing of IPv6 endpoint with invalid port character separator
* added limited support for new x.pe parameter from BEP 9 * added limited support for new x.pe parameter from BEP 9

View File

@ -306,7 +306,7 @@ namespace libtorrent
// also be stored in resume data. If you want the save path saved in // also be stored in resume data. If you want the save path saved in
// the resume data to be used, you need to set the // the resume data to be used, you need to set the
// flag_use_resume_save_path flag. // flag_use_resume_save_path flag.
// //
// .. note:: // .. note::
// On windows this path (and other paths) are interpreted as UNC // On windows this path (and other paths) are interpreted as UNC
// paths. This means they must use backslashes as directory separators // paths. This means they must use backslashes as directory separators
@ -377,6 +377,7 @@ namespace libtorrent
// flags controlling aspects of this torrent and how it's added. See // flags controlling aspects of this torrent and how it's added. See
// flags_t for details. // flags_t for details.
//
// .. note:: // .. note::
// The ``flags`` field is initialized with default flags by the // The ``flags`` field is initialized with default flags by the
// constructor. In order to preserve default behavior when clearing or // constructor. In order to preserve default behavior when clearing or

View File

@ -174,6 +174,7 @@ restart_response:
if (m_state == read_status) if (m_state == read_status)
{ {
TORRENT_ASSERT(!m_finished); TORRENT_ASSERT(!m_finished);
TORRENT_ASSERT(pos <= recv_buffer.end);
char const* newline = std::find(pos, recv_buffer.end, '\n'); char const* newline = std::find(pos, recv_buffer.end, '\n');
// if we don't have a full line yet, wait. // if we don't have a full line yet, wait.
if (newline == recv_buffer.end) if (newline == recv_buffer.end)
@ -194,6 +195,7 @@ restart_response:
char const* line = pos; char const* line = pos;
++newline; ++newline;
TORRENT_ASSERT(newline >= pos);
int incoming = int(newline - pos); int incoming = int(newline - pos);
m_recv_pos += incoming; m_recv_pos += incoming;
boost::get<1>(ret) += newline - (m_recv_buffer.begin + start_pos); boost::get<1>(ret) += newline - (m_recv_buffer.begin + start_pos);
@ -227,6 +229,7 @@ restart_response:
if (m_state == read_header) if (m_state == read_header)
{ {
TORRENT_ASSERT(!m_finished); TORRENT_ASSERT(!m_finished);
TORRENT_ASSERT(pos <= recv_buffer.end);
char const* newline = std::find(pos, recv_buffer.end, '\n'); char const* newline = std::find(pos, recv_buffer.end, '\n');
std::string line; std::string line;
@ -277,6 +280,12 @@ restart_response:
if (name == "content-length") if (name == "content-length")
{ {
m_content_length = strtoll(value.c_str(), 0, 10); m_content_length = strtoll(value.c_str(), 0, 10);
if (m_content_length < 0)
{
m_state = error_state;
error = true;
return ret;
}
} }
else if (name == "connection") else if (name == "connection")
{ {
@ -294,12 +303,24 @@ restart_response:
if (string_begins_no_case("bytes ", ptr)) ptr += 6; if (string_begins_no_case("bytes ", ptr)) ptr += 6;
char* end; char* end;
m_range_start = strtoll(ptr, &end, 10); m_range_start = strtoll(ptr, &end, 10);
if (m_range_start < 0)
{
m_state = error_state;
error = true;
return ret;
}
if (end == ptr) success = false; if (end == ptr) success = false;
else if (*end != '-') success = false; else if (*end != '-') success = false;
else else
{ {
ptr = end + 1; ptr = end + 1;
m_range_end = strtoll(ptr, &end, 10); m_range_end = strtoll(ptr, &end, 10);
if (m_range_end < 0)
{
m_state = error_state;
error = true;
return ret;
}
if (end == ptr) success = false; if (end == ptr) success = false;
} }
@ -318,6 +339,7 @@ restart_response:
} }
TORRENT_ASSERT(m_recv_pos <= recv_buffer.left()); TORRENT_ASSERT(m_recv_pos <= recv_buffer.left());
TORRENT_ASSERT(pos <= recv_buffer.end);
newline = std::find(pos, recv_buffer.end, '\n'); newline = std::find(pos, recv_buffer.end, '\n');
} }
boost::get<1>(ret) += newline - (m_recv_buffer.begin + start_pos); boost::get<1>(ret) += newline - (m_recv_buffer.begin + start_pos);
@ -347,6 +369,12 @@ restart_response:
int header_size; int header_size;
if (parse_chunk_header(buf, &chunk_size, &header_size)) if (parse_chunk_header(buf, &chunk_size, &header_size))
{ {
if (chunk_size < 0)
{
m_state = error_state;
error = true;
return ret;
}
if (chunk_size > 0) if (chunk_size > 0)
{ {
std::pair<boost::int64_t, boost::int64_t> chunk_range(m_cur_chunk_end + header_size std::pair<boost::int64_t, boost::int64_t> chunk_range(m_cur_chunk_end + header_size
@ -419,6 +447,7 @@ restart_response:
bool http_parser::parse_chunk_header(buffer::const_interval buf bool http_parser::parse_chunk_header(buffer::const_interval buf
, boost::int64_t* chunk_size, int* header_size) , boost::int64_t* chunk_size, int* header_size)
{ {
TORRENT_ASSERT(buf.begin <= buf.end);
char const* pos = buf.begin; char const* pos = buf.begin;
// ignore one optional new-line. This is since each chunk // ignore one optional new-line. This is since each chunk
@ -429,6 +458,7 @@ restart_response:
if (pos < buf.end && pos[0] == '\n') ++pos; if (pos < buf.end && pos[0] == '\n') ++pos;
if (pos == buf.end) return false; if (pos == buf.end) return false;
TORRENT_ASSERT(pos <= buf.end);
char const* newline = std::find(pos, buf.end, '\n'); char const* newline = std::find(pos, buf.end, '\n');
if (newline == buf.end) return false; if (newline == buf.end) return false;
++newline; ++newline;
@ -441,6 +471,8 @@ restart_response:
// first, read the chunk length // first, read the chunk length
*chunk_size = strtoll(pos, 0, 16); *chunk_size = strtoll(pos, 0, 16);
if (*chunk_size < 0) return true;
if (*chunk_size != 0) if (*chunk_size != 0)
{ {
*header_size = newline - buf.begin; *header_size = newline - buf.begin;

View File

@ -361,29 +361,6 @@ TORRENT_TEST(http_parser)
TEST_EQUAL(parser.headers().find("test2")->second, "bar"); TEST_EQUAL(parser.headers().find("test2")->second, "bar");
} }
// test chunked encoding
parser.reset();
char const* chunked_input =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"4\r\ntest\r\n4\r\n1234\r\n10\r\n0123456789abcdef\r\n"
"0\r\n\r\n";
received = feed_bytes(parser, chunked_input);
TEST_EQUAL(strlen(chunked_input), 24 + 94)
TEST_CHECK(received == make_tuple(24, 94, false));
TEST_CHECK(parser.finished());
char mutable_buffer[100];
memcpy(mutable_buffer, parser.get_body().begin, parser.get_body().left());
int len = parser.collapse_chunk_headers(mutable_buffer, parser.get_body().left());
TEST_CHECK(std::equal(mutable_buffer, mutable_buffer + len, "test12340123456789abcdef"));
// test url parsing // test url parsing
error_code ec; error_code ec;
@ -476,3 +453,119 @@ TORRENT_TEST(http_parser)
TEST_EQUAL(is_redirect(400), false); TEST_EQUAL(is_redirect(400), false);
} }
TORRENT_TEST(chunked_encoding)
{
char const* chunked_input =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"4\r\ntest\r\n4\r\n1234\r\n10\r\n0123456789abcdef\r\n"
"0\r\n\r\n";
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, chunked_input);
TEST_EQUAL(strlen(chunked_input), 24 + 94)
TEST_CHECK(received == make_tuple(24, 94, false));
TEST_CHECK(parser.finished());
char mutable_buffer[100];
memcpy(mutable_buffer, parser.get_body().begin, parser.get_body().left());
int len = parser.collapse_chunk_headers(mutable_buffer, parser.get_body().left());
TEST_CHECK(std::equal(mutable_buffer, mutable_buffer + len, "test12340123456789abcdef"));
}
TORRENT_TEST(invalid_content_length)
{
char const* chunked_input =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Length: -45345\r\n"
"\r\n";
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, chunked_input);
TEST_CHECK(boost::get<2>(received) == true);
}
TORRENT_TEST(invalid_chunked)
{
char const* chunked_input =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"-53465234545\r\n"
"foobar";
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, chunked_input);
TEST_CHECK(boost::get<2>(received) == true);
}
TORRENT_TEST(invalid_content_range_start)
{
char const* chunked_input =
"HTTP/1.1 206 OK\n"
"Content-Range: bYTes -3-4\n"
"\n";
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, chunked_input);
TEST_CHECK(boost::get<2>(received) == true);
}
TORRENT_TEST(invalid_content_range_end)
{
char const* chunked_input =
"HTTP/1.1 206 OK\n"
"Content-Range: bYTes 3--434\n"
"\n";
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, chunked_input);
TEST_CHECK(boost::get<2>(received) == true);
}
TORRENT_TEST(invalid_chunk_afl)
{
boost::uint8_t const invalid_chunked_input[] = {
0x48, 0x6f, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HoTP/1.1 200 OK
0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d, // Cont-Length: 20
0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x2d, 0x4c, 0x65, // Contente: tn
0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, // Transfer-Encoding: chunked
0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, //
0x74, 0x65, 0x3a, 0x20, 0x74, 0x6e, 0x0d, 0x0a, //
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, //
0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, // -89abc9abcdef
0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, // <20>
0x65, 0x64, 0x0d, 0x0a, 0x0d, 0x0d, 0x0a, 0x0d, // T<><54><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>def
0x0a, 0x0a, 0x2d, 0x38, 0x39, 0x61, 0x62, 0x63, // <20>
0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x0d, // T<><54><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>est-headyr: foobar
0x0a, 0xd6, 0x0d, 0x0a, 0x54, 0xbd, 0xbd, 0xbd,
0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0x64,
0x65, 0x66, 0x0d, 0x0a, 0xd6, 0x0d, 0x0a, 0x54,
0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
0xbd, 0xbd, 0xbd, 0x65, 0x73, 0x74, 0x2d, 0x68,
0x65, 0x61, 0x64, 0x79, 0x72, 0x3a, 0x20, 0x66,
0x6f, 0x6f, 0x62, 0x61, 0x72, 0x0d, 0x0a, 0x0d,
0x0a, 0x00
};
http_parser parser;
boost::tuple<int, int, bool> const received
= feed_bytes(parser, reinterpret_cast<char const*>(invalid_chunked_input));
TEST_CHECK(boost::get<2>(received) == true);
}