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: Version 301:
* Fix Travis CI bug. * Fix Travis CI bug.

View File

@@ -56,19 +56,25 @@ struct stream_base
class pending_guard class pending_guard
{ {
bool& b_; bool* b_ = nullptr;
bool clear_ = true; bool clear_ = true;
public: public:
~pending_guard() ~pending_guard()
{ {
if(clear_) if(clear_ && b_)
b_ = false; *b_ = false;
}
pending_guard()
: b_(nullptr)
, clear_(true)
{
} }
explicit explicit
pending_guard(bool& b) pending_guard(bool& b)
: b_(b) : b_(&b)
{ {
// If this assert goes off, it means you are attempting // If this assert goes off, it means you are attempting
// to issue two of the same asynchronous I/O operation // 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 // calls to async_read_some. Only one pending call of
// each I/O type (read and write) is permitted. // each I/O type (read and write) is permitted.
// //
BOOST_ASSERT(! b_); BOOST_ASSERT(! *b_);
b_ = true; *b_ = true;
} }
pending_guard( 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 void
reset() reset()
{ {
BOOST_ASSERT(clear_); BOOST_ASSERT(clear_);
b_ = false; if (b_)
*b_ = false;
clear_ = false; clear_ = false;
} }
}; };

View File

@@ -269,6 +269,8 @@ class transfer_op
std::move(*this)); std::move(*this));
} }
static bool never_pending_;
public: public:
template<class Handler_> template<class Handler_>
transfer_op( transfer_op(
@@ -278,10 +280,25 @@ public:
: async_base<Handler, Executor>( : async_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor()) std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_) , impl_(s.impl_)
, pg_(state().pending) , pg_()
, b_(b) , 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 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 void
run() run()
{ {
@@ -1382,6 +1410,7 @@ public:
boost::ignore_unused(&basic_stream_test::testAwaitableCompilation); boost::ignore_unused(&basic_stream_test::testAwaitableCompilation);
#endif #endif
boost::ignore_unused(&basic_stream_test::testConnectionConditionArgs); boost::ignore_unused(&basic_stream_test::testConnectionConditionArgs);
testIssue2065();
} }
}; };