make the chunk header parser a bit more strict and accurate (and not rely on there being a 0 termiator character)

This commit is contained in:
arvidn 2017-09-30 19:30:48 +02:00 committed by Arvid Norberg
parent 2e6150de52
commit 12e92aeeb4
2 changed files with 107 additions and 38 deletions

View File

@ -34,9 +34,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cinttypes>
#include "libtorrent/config.hpp"
#include "libtorrent/http_parser.hpp"
#include "libtorrent/hex.hpp" // for hex_to_int
#include "libtorrent/assert.hpp"
#include "libtorrent/parse_url.hpp" // for parse_url_components
#include "libtorrent/string_util.hpp" // for ensure_trailing_slash, to_lower
@ -373,10 +375,10 @@ restart_response:
}
header_size -= m_partial_chunk_header;
m_partial_chunk_header = 0;
// std::fprintf(stderr, "parse_chunk_header(%d, -> %d, -> %d) -> %d\n"
// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %d\n"
// std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n"
// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %" PRId64 "\n"
// " content-length = %d\n"
// , buf.size(), int(chunk_size), header_size, 1, incoming, int(m_recv_pos)
// , int(buf.size()), chunk_size, header_size, 1, incoming, int(m_recv_pos)
// , m_cur_chunk_end, int(m_content_length));
}
else
@ -384,10 +386,10 @@ restart_response:
m_partial_chunk_header += incoming;
header_size = incoming;
// std::fprintf(stderr, "parse_chunk_header(%d, -> %d, -> %d) -> %d\n"
// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %d\n"
// std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n"
// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %" PRId64 "\n"
// " content-length = %d\n"
// , buf.size(), int(chunk_size), header_size, 0, incoming, int(m_recv_pos)
// , int(buf.size()), chunk_size, header_size, 0, incoming, int(m_recv_pos)
// , m_cur_chunk_end, int(m_content_length));
}
m_chunk_header_size += header_size;
@ -428,6 +430,9 @@ restart_response:
return ret;
}
// this function signals error by assigning a negative value to "chunk_size"
// the return value indicates whether enough data is available in "buf" to
// completely parse the chunk header. Returning false means we need more data
bool http_parser::parse_chunk_header(span<char const> buf
, std::int64_t* chunk_size, int* header_size)
{
@ -452,15 +457,36 @@ restart_response:
// there are extra tail headers, which is terminated by an
// empty line
*header_size = int(newline - buf.data());
// first, read the chunk length
*chunk_size = std::strtoll(pos, nullptr, 16);
if (*chunk_size < 0) return true;
std::int64_t size = 0;
for (char const* i = pos; i != newline; ++i)
{
if (*i == '\r') continue;
if (*i == '\n') continue;
if (*i == ';') break;
int const digit = aux::hex_to_int(*i);
if (digit < 0)
{
*chunk_size = -1;
return true;
}
if (size >= std::numeric_limits<std::int64_t>::max() / 16)
{
*chunk_size = -1;
return true;
}
size *= 16;
size += digit;
}
*chunk_size = size;
if (*chunk_size != 0)
{
*header_size = int(newline - buf.data());
// the newline alone is two bytes
TORRENT_ASSERT(newline - buf.data() > 2);
// the newline is at least 1 byte, and the length-prefix is at least 1
// byte
TORRENT_ASSERT(newline - buf.data() >= 2);
return true;
}

View File

@ -33,13 +33,13 @@ POSSIBILITY OF SUCH DAMAGE.
#include "test.hpp"
#include "libtorrent/http_parser.hpp"
#include "libtorrent/parse_url.hpp"
#include "libtorrent/string_view.hpp"
#include <tuple>
using namespace lt;
// TODO: 3 use span here instead of zero-terminated string
std::tuple<int, int, bool> feed_bytes(http_parser& parser, char const* str)
std::tuple<int, int, bool> feed_bytes(http_parser& parser, string_view str)
{
std::tuple<int, int, bool> ret(0, 0, false);
std::tuple<int, int, bool> prev(0, 0, false);
@ -47,12 +47,12 @@ std::tuple<int, int, bool> feed_bytes(http_parser& parser, char const* str)
{
ret = std::make_tuple(0, 0, false);
parser.reset();
span<char const> recv_buf(str, 0);
string_view recv_buf;
for (;;)
{
int chunk_size = std::min(chunks, int(strlen(recv_buf.end())));
int const chunk_size = std::min(chunks, int(str.size() - recv_buf.size()));
if (chunk_size == 0) break;
recv_buf = span<char const>(recv_buf.data(), recv_buf.size() + chunk_size);
recv_buf = str.substr(0, recv_buf.size() + chunk_size);
int payload, protocol;
bool error = false;
std::tie(payload, protocol) = parser.incoming(recv_buf, error);
@ -64,7 +64,7 @@ std::tuple<int, int, bool> feed_bytes(http_parser& parser, char const* str)
TEST_CHECK(prev == std::make_tuple(0, 0, false) || ret == prev || std::get<2>(ret));
if (!std::get<2>(ret))
{
TEST_EQUAL(std::get<0>(ret) + std::get<1>(ret), int(strlen(str)));
TEST_EQUAL(std::get<0>(ret) + std::get<1>(ret), int(str.size()));
}
prev = ret;
@ -96,7 +96,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(!parser.finished());
char const* upnp_response =
char const upnp_response[] =
"HTTP/1.1 200 OK\r\n"
"ST:upnp:rootdevice\r\n"
"USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice\r\n"
@ -122,7 +122,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(!parser.finished());
char const* http1_response =
char const http1_response[] =
"HTTP/1.0 200 OK\r\n"
"Cache-Control: max-age=180\r\n"
"DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n";
@ -138,7 +138,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(!parser.finished());
char const* close_response =
char const close_response[] =
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n";
@ -154,7 +154,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(!parser.finished());
char const* keep_alive_response =
char const keep_alive_response[] =
"HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n";
@ -169,7 +169,7 @@ TORRENT_TEST(http_parser)
parser.reset();
TEST_CHECK(!parser.finished());
char const* upnp_notify =
char const upnp_notify[] =
"NOTIFY * HTTP/1.1\r\n"
"Host:239.255.255.250:1900\r\n"
"NT:urn:schemas-upnp-org:device:MediaServer:1\r\n"
@ -189,7 +189,7 @@ TORRENT_TEST(http_parser)
parser.reset();
TEST_CHECK(!parser.finished());
char const* bt_lsd = "BT-SEARCH * HTTP/1.1\r\n"
char const bt_lsd[] = "BT-SEARCH * HTTP/1.1\r\n"
"Host: 239.192.152.143:6771\r\n"
"Port: 6881\r\n"
"Infohash: 12345678901234567890\r\n"
@ -209,7 +209,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(!parser.finished());
// test chunked encoding
char const* chunked_test = "HTTP/1.1 200 OK\r\n"
char const chunked_test[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 20\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
@ -240,7 +240,7 @@ TORRENT_TEST(http_parser)
TEST_CHECK(cmp == parser.chunks());
// make sure we support trackers with incorrect line endings
char const* tracker_response =
char const tracker_response[] =
"HTTP/1.1 200 OK\n"
"content-length: 5\n"
"content-type: test/plain\n"
@ -256,7 +256,7 @@ TORRENT_TEST(http_parser)
// make sure we support content-range responses
// and that we're case insensitive
char const* web_seed_response =
char const web_seed_response[] =
"HTTP/1.1 206 OK\n"
"contEnt-rAngE: bYTes 0-4\n"
"conTent-TyPe: test/plain\n"
@ -272,7 +272,7 @@ TORRENT_TEST(http_parser)
parser.reset();
// test invalid content range
char const* invalid_range_response =
char const invalid_range_response[] =
"HTTP/1.1 206 OK\n"
"content-range: bytes 4-0\n"
"content-type: test/plain\n"
@ -286,7 +286,7 @@ TORRENT_TEST(http_parser)
parser.reset();
// test invalid status line
char const* invalid_status_response =
char const invalid_status_response[] =
"HTTP/1.1 206\n"
"content-range: bytes 4-0\n"
"content-type: test/plain\n"
@ -300,7 +300,7 @@ TORRENT_TEST(http_parser)
parser.reset();
// test invalid status line 2
char const* invalid_status_response2 =
char const invalid_status_response2[] =
"HTTP/1.1\n"
"content-range: bytes 4-0\n"
"content-type: test/plain\n"
@ -315,7 +315,7 @@ TORRENT_TEST(http_parser)
// make sure we support content-range responses
// and that we're case insensitive
char const* one_hundred_response =
char const one_hundred_response[] =
"HTTP/1.1 100 Continue\n"
"\r\n"
"HTTP/1.1 200 OK\n"
@ -499,7 +499,7 @@ TORRENT_TEST(http_parser)
TORRENT_TEST(chunked_encoding)
{
char const* chunked_input =
char const chunked_input[] =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: text/plain\r\n"
@ -525,7 +525,7 @@ TORRENT_TEST(chunked_encoding)
TORRENT_TEST(invalid_content_length)
{
char const* chunked_input =
char const chunked_input[] =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Length: -45345\r\n"
@ -540,7 +540,7 @@ TORRENT_TEST(invalid_content_length)
TORRENT_TEST(invalid_chunked)
{
char const* chunked_input =
char const chunked_input[] =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
@ -556,7 +556,7 @@ TORRENT_TEST(invalid_chunked)
TORRENT_TEST(invalid_content_range_start)
{
char const* chunked_input =
char const chunked_input[] =
"HTTP/1.1 206 OK\n"
"Content-Range: bYTes -3-4\n"
"\n";
@ -570,7 +570,7 @@ TORRENT_TEST(invalid_content_range_start)
TORRENT_TEST(invalid_content_range_end)
{
char const* chunked_input =
char const chunked_input[] =
"HTTP/1.1 206 OK\n"
"Content-Range: bYTes 3--434\n"
"\n";
@ -582,7 +582,27 @@ TORRENT_TEST(invalid_content_range_end)
TEST_CHECK(std::get<2>(received) == true);
}
TORRENT_TEST(invalid_chunk_afl)
TORRENT_TEST(missing_chunked_header)
{
char const input[] =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"\n";
// make the inpout not be null terminated. If the parser reads off the end,
// address sanitizer will trigger
char chunked_input[sizeof(input)-1];
std::memcpy(chunked_input, input, sizeof(chunked_input));
http_parser parser;
std::tuple<int, int, bool> const received
= feed_bytes(parser, {chunked_input, sizeof(chunked_input)});
TEST_CHECK(std::get<2>(received) == false);
}
TORRENT_TEST(invalid_chunk_1)
{
std::uint8_t const invalid_chunked_input[] = {
0x48, 0x6f, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HoTP/1.1 200 OK
@ -609,8 +629,31 @@ TORRENT_TEST(invalid_chunk_afl)
http_parser parser;
std::tuple<int, int, bool> const received
= feed_bytes(parser, reinterpret_cast<char const*>(invalid_chunked_input));
= feed_bytes(parser, {reinterpret_cast<char const*>(invalid_chunked_input), sizeof(invalid_chunked_input)});
TEST_CHECK(std::get<2>(received) == true);
TEST_CHECK(std::get<2>(received) == false);
}
TORRENT_TEST(invalid_chunk_2)
{
std::uint8_t const invalid_chunked_input[] = {
0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31,
0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0a, // HTTP/1.1 20x00, OK.
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, // Transfer-Encodin
0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, // g: chunked.Date:
0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, // Sat, 0x31, Aug 200
0x65, 0x64, 0x0a, 0x44, 0x61, 0x74, 0x65, 0x3a, // 2 00:24:0x08, GMT.C
0x20, 0x53, 0x61, 0x74, 0x2c, 0x20, 0x33, 0x31, // onnection: close
0x20, 0x41, 0x75, 0x67, 0x20, 0x32, 0x30, 0x30,
0x32, 0x20, 0x30, 0x30, 0x3a, 0x32, 0x34, 0x3a,
0x30, 0x38, 0x20, 0x47, 0x4d, 0x54, 0x0a, 0x43,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x3a, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65,
0x0a, 0x0a, 0x31, 0x0a, 0x72, 0x0a, 0x30, 0x0a,
0x0a
};
http_parser parser;
feed_bytes(parser, {reinterpret_cast<char const*>(invalid_chunked_input), sizeof(invalid_chunked_input)});
}