Add basic_parser header and body limits:

fix #550

* default body limits are now 1MB/8MB
* default header limit is 8KB

Actions Required:

* Call body_limit and/or header_limit as needed to adjust the
  limits to suitable values if the defaults are insufficient.
This commit is contained in:
Vinnie Falco
2017-06-27 21:00:53 -07:00
parent 3c4ae2a098
commit 788550e833
8 changed files with 229 additions and 17 deletions

View File

@ -1,15 +1,20 @@
Version 70:
* Serialize in one step when possible
* Add basic_parser header and body limits
API Changes:
* Rename to message::base
* basic_parser default limits are now 1MB/8MB
Actions Required:
* Change calls to message::header_part() with message::base()
* Call body_limit and/or header_limit as needed to adjust the
limits to suitable values if the defaults are insufficient.
--------------------------------------------------------------------------------
Version 69:

View File

@ -23,7 +23,7 @@ if the underlying connection is closed),
[heading Parser Options]
The parser provides two options which may be set before parsing begins:
The parser provides a few options which may be set before parsing begins:
[table Parser Options
[[Name][Default][Description]]
@ -58,6 +58,27 @@ The parser provides two options which may be set before parsing begins:
no body is expected. The parser will consider the message complete
after the header has been received.
]]
[[
[link beast.ref.beast__http__basic_parser.body_limit `body_limit`]
][
1MB/8MB
][
This function sets the maximum allowed size of the content body.
When a body larger than the specified size is detected, an error
is generated and parsing terminates. This setting helps protect
servers from resource exhaustion attacks. The default limit when
parsing requests is 1MB, and for parsing responses 8MB.
]]
[[
[link beast.ref.beast__http__basic_parser.header_limit `header_limit`]
][
8KB
][
This function sets the maximum allowed size of the header
including all field name, value, and delimiter characters
and also including the CRLF sequences in the serialized
input.
]]
]

View File

@ -17,6 +17,7 @@
#include <boost/asio/buffer.hpp>
#include <boost/optional.hpp>
#include <boost/assert.hpp>
#include <limits>
#include <memory>
#include <utility>
@ -105,30 +106,50 @@ class basic_parser
static unsigned constexpr flagUpgrade = 1<< 12;
static unsigned constexpr flagFinalChunk = 1<< 13;
static
std::uint64_t
default_body_limit(std::true_type)
{
// limit for requests
return 1 * 1024 * 1024; // 1MB
}
static
std::uint64_t
default_body_limit(std::false_type)
{
// limit for responses
return 8 * 1024 * 1024; // 8MB
}
std::uint64_t body_limit_; // max payload body
std::uint64_t len_; // size of chunk or body
std::unique_ptr<char[]> buf_;
std::size_t buf_len_ = 0;
std::size_t skip_ = 0; // search from here
state state_ = state::nothing_yet;
std::unique_ptr<char[]> buf_; // temp storage
std::size_t buf_len_ = 0; // size of buf_
std::size_t skip_ = 0; // resume search here
std::uint32_t
header_limit_ = 8192; // max header size
state state_ = // initial state
state::nothing_yet;
unsigned f_ = 0; // flags
public:
/// `true` if this parser parses requests, `false` for responses.
using is_request =
std::integral_constant<bool, isRequest>;
/// Copy constructor (disallowed)
basic_parser(basic_parser const&) = delete;
/// Copy assignment (disallowed)
basic_parser& operator=(basic_parser const&) = delete;
/// Default constructor
basic_parser() = default;
/// `true` if this parser parses requests, `false` for responses.
using is_request =
std::integral_constant<bool, isRequest>;
/// Destructor
~basic_parser() = default;
/// Default constructor
basic_parser();;
/** Move constructor
After the move, the only valid operation on the
@ -246,6 +267,58 @@ public:
return (f_ & flagNeedEOF) != 0;
}
/** Set the limit on the payload body.
This function sets the maximum allowed size of the payload body,
before any encodings except chunked have been removed. Depending
on the message semantics, one of these cases will apply:
@li The Content-Length is specified and exceeds the limit. In
this case the result @ref error::body_limit is returned
immediately after the header is parsed.
@li The Content-Length is unspecified and the chunked encoding
is not specified as the last encoding. In this case the end of
message is determined by the end of file indicator on the
associated stream or input source. If a sufficient number of
body payload octets are presented to the parser to exceed the
configured limit, the parse fails with the result
@ref error::body_limit
@li The Transfer-Encoding specifies the chunked encoding as the
last encoding. In this case, when the number of payload body
octets produced by removing the chunked encoding exceeds
the configured limit, the parse fails with the result
@ref error::body_limit.
Setting the limit after any body octets have been parsed
results in undefined behavior.
The default limit is 1MB for requests and 8MB for responses.
@param v The payload body limit to set
*/
void
body_limit(std::uint64_t v)
{
body_limit_ = v;
}
/** Set a limit on the total size of the header.
This function sets the maximum allowed size of the header
including all field name, value, and delimiter characters
and also including the CRLF sequences in the serialized
input. If the end of the header is not found within the
limit of the header size, the error @ref error::header_limit
is returned by @ref put.
*/
void
header_limit(std::uint32_t v)
{
header_limit_ = v;
}
/// Returns `true` if the eager parse option is set.
bool
eager() const

View File

@ -80,6 +80,20 @@ enum class error
*/
buffer_overflow,
/** Header limit exceeded.
The parser detected an incoming message header which
exceeded a configured limit.
*/
header_limit,
/** Body limit exceeded.
The parser detected an incoming message body which
exceeded a configured limit.
*/
body_limit,
/** A memory allocation failed.
When basic_fields throws std::bad_alloc, it is

View File

@ -55,12 +55,21 @@ skip_ows_rev2(
} // detail
template<bool isRequest, class Derived>
basic_parser<isRequest, Derived>::
basic_parser()
: body_limit_(
default_body_limit(is_request{}))
{
}
template<bool isRequest, class Derived>
template<class OtherDerived>
basic_parser<isRequest, Derived>::
basic_parser(basic_parser<
isRequest, OtherDerived>&& other)
: len_(other.len_)
: body_limit_(other.body_limit_)
, len_(other.len_)
, buf_(std::move(other.buf_))
, buf_len_(other.buf_len_)
, skip_(other.skip_)
@ -294,9 +303,11 @@ basic_parser<isRequest, Derived>::
parse_header(char const*& p,
std::size_t n, error_code& ec)
{
if( n > header_limit_)
n = header_limit_;
if(n < skip_ + 4)
{
ec = http::error::need_more;
ec = error::need_more;
return;
}
auto const term =
@ -304,6 +315,11 @@ parse_header(char const*& p,
if(! term)
{
skip_ = n - 3;
if(skip_ + 4 > header_limit_)
{
ec = error::header_limit;
return;
}
ec = http::error::need_more;
return;
}
@ -528,6 +544,12 @@ basic_parser<isRequest, Derived>::
parse_body_to_eof(char const*& p,
std::size_t n, error_code& ec)
{
if(n > body_limit_)
{
ec = error::body_limit;
return;
}
body_limit_ = body_limit_ - n;
impl().on_data(string_view{p, n}, ec);
if(ec)
return;
@ -596,6 +618,12 @@ parse_chunk_header(char const*& p0,
}
if(v != 0)
{
if(v > body_limit_)
{
ec = error::body_limit;
return;
}
body_limit_ -= v;
if(*p == ';')
{
// VFALCO TODO Validate extension
@ -868,6 +896,12 @@ do_field(field f,
return;
}
if(v > body_limit_)
{
ec = error::body_limit;
return;
}
ec.assign(0, ec.category());
len_ = v;
f_ |= flagContentLength;

View File

@ -44,6 +44,8 @@ public:
case error::unexpected_body: return "unexpected body";
case error::need_buffer: return "need buffer";
case error::buffer_overflow: return "buffer overflow";
case error::header_limit: return "header limit exceeded";
case error::body_limit: return "body limit exceeded";
case error::bad_alloc: return "bad alloc";
case error::bad_line_ending: return "bad line ending";
case error::bad_method: return "bad method";

View File

@ -615,7 +615,6 @@ public:
length(c("1\r\n"), 1);
length(c("01\r\n"), 1);
length(c("9\r\n"), 9);
length(c("123456789\r\n"), 123456789);
length(c("42 \r\n"), 42);
length(c("42\t\r\n"), 42);
length(c("42 \t \r\n"), 42);
@ -923,6 +922,68 @@ public:
BEAST_EXPECT(p.body == "*****");
}
void
testLimits()
{
{
multi_buffer b;
ostream(b) <<
"POST / HTTP/1.1\r\n"
"Content-Length: 2\r\n"
"\r\n"
"**";
error_code ec;
test_parser<true> p;
p.header_limit(10);
p.eager(true);
p.put(b.data(), ec);
BEAST_EXPECTS(ec == error::header_limit, ec.message());
}
{
multi_buffer b;
ostream(b) <<
"POST / HTTP/1.1\r\n"
"Content-Length: 2\r\n"
"\r\n"
"**";
error_code ec;
test_parser<true> p;
p.body_limit(1);
p.eager(true);
p.put(b.data(), ec);
BEAST_EXPECTS(ec == error::body_limit, ec.message());
}
{
multi_buffer b;
ostream(b) <<
"HTTP/1.1 200 OK\r\n"
"\r\n"
"**";
error_code ec;
test_parser<false> p;
p.body_limit(1);
p.eager(true);
p.put(b.data(), ec);
BEAST_EXPECTS(ec == error::body_limit, ec.message());
}
{
multi_buffer b;
ostream(b) <<
"POST / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"2\r\n"
"**\r\n"
"0\r\n\r\n";
error_code ec;
test_parser<true> p;
p.body_limit(1);
p.eager(true);
p.put(b.data(), ec);
BEAST_EXPECTS(ec == error::body_limit, ec.message());
}
}
//--------------------------------------------------------------------------
template<bool isRequest, class Derived>
@ -1088,6 +1149,7 @@ public:
testUpgradeField();
testBody();
testSplit();
testLimits();
testIssue430();
testIssue452();
testIssue496();

View File

@ -42,6 +42,7 @@ public:
check("beast.http", error::unexpected_body);
check("beast.http", error::need_buffer);
check("beast.http", error::buffer_overflow);
check("beast.http", error::body_limit);
check("beast.http", error::bad_alloc);
check("beast.http", error::bad_line_ending);