Fix assert when basic_stream used as underlying of ssl::stream with 0-length write

Fixes #2065
Closes #2078
This commit is contained in:
Richard Hodges
2020-09-03 11:43:36 +02:00
parent 4eb7d06d5d
commit 7811f4c52f
4 changed files with 85 additions and 9 deletions

View File

@ -1,3 +1,9 @@
Version XXX:
* Fix assert when basic_stream used as underlying of ssl::stream with zero-length write.
--------------------------------------------------------------------------------
Version 301:
* Fix Travis CI bug.

View File

@ -56,19 +56,25 @@ struct stream_base
class pending_guard
{
bool& b_;
bool* b_ = nullptr;
bool clear_ = true;
public:
~pending_guard()
{
if(clear_)
b_ = false;
if(clear_ && b_)
*b_ = false;
}
pending_guard()
: b_(nullptr)
, clear_(true)
{
}
explicit
pending_guard(bool& b)
: b_(b)
: b_(&b)
{
// If this assert goes off, it means you are attempting
// to issue two of the same asynchronous I/O operation
@ -77,8 +83,8 @@ struct stream_base
// calls to async_read_some. Only one pending call of
// each I/O type (read and write) is permitted.
//
BOOST_ASSERT(! b_);
b_ = true;
BOOST_ASSERT(! *b_);
*b_ = true;
}
pending_guard(
@ -89,11 +95,29 @@ struct stream_base
{
}
void assign(bool& b)
{
BOOST_ASSERT(!b_);
BOOST_ASSERT(clear_);
b_ = &b;
// If this assert goes off, it means you are attempting
// to issue two of the same asynchronous I/O operation
// at the same time, without waiting for the first one
// to complete. For example, attempting two simultaneous
// calls to async_read_some. Only one pending call of
// each I/O type (read and write) is permitted.
//
BOOST_ASSERT(! *b_);
*b_ = true;
}
void
reset()
{
BOOST_ASSERT(clear_);
b_ = false;
if (b_)
*b_ = false;
clear_ = false;
}
};

View File

@ -269,6 +269,8 @@ class transfer_op
std::move(*this));
}
static bool never_pending_;
public:
template<class Handler_>
transfer_op(
@ -278,10 +280,25 @@ public:
: async_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_)
, pg_(state().pending)
, pg_()
, b_(b)
{
(*this)({});
if (buffer_bytes(b_) == 0 && state().pending)
{
// Workaround:
// Corner case discovered in https://github.com/boostorg/beast/issues/2065
// Enclosing SSL stream wishes to complete a 0-length write early by
// executing a 0-length read against the underlying stream.
// This can occur even if an existing async_read is in progress.
// In this specific case, we will complete the async op with no error
// in order to prevent assertions and/or internal corruption of the basic_stream
this->complete(false, error_code(), 0);
}
else
{
pg_.assign(state().pending);
(*this)({});
}
}
void

View File

@ -1366,6 +1366,34 @@ public:
};
}
void
testIssue2065()
{
using stream_type = basic_stream<tcp,
net::io_context::executor_type>;
char buf[4];
net::io_context ioc;
std::memset(buf, 0, sizeof(buf));
net::mutable_buffer mb(buf, sizeof(buf));
auto const ep = net::ip::tcp::endpoint(
net::ip::make_address("127.0.0.1"), 0);
// async_read_some
{
// success
test_server srv("*", ep, log);
stream_type s(ioc);
s.socket().connect(srv.local_endpoint());
s.expires_never();
s.async_read_some(mb, handler({}, 1));
s.async_read_some(net::buffer(buf, 0), handler({}, 0));
ioc.run();
ioc.restart();
}
}
void
run()
{
@ -1382,6 +1410,7 @@ public:
boost::ignore_unused(&basic_stream_test::testAwaitableCompilation);
#endif
boost::ignore_unused(&basic_stream_test::testConnectionConditionArgs);
testIssue2065();
}
};