Skip to content

Commit a2a5da3

Browse files
tls/ossl: Add concurrency protections around output
This commit makes the following changes to add better concurrency protection around the output path: * No longer treats the BIO methods as friend functions * Adds an _output_in_progress flag It was originally understood that data being written to the output BIO would only occur while the TLS session's _out_sem semaphore was held. Assertions ensuring there were no concurrent output operations occuring provided guarantees that concurrent writes would not occur. Anytime data was written to the output BIO of the TLS session, it was followed by a call to wait_for_output() which would resolve only after the write completed. However, we have come to learn that data may be written outside of the protections afforded by the _out_sem semaphore during the get() method. This would, rarely, result in assertions firing during put(). This additionally could cause, in rare circumstances, a nullptr dereference as this extraneous write would overwrite the packet of a write in progress. The change to use the _output_in_progress flag ensures that the output operation has fully completed before attempting another write to the socket. Additionally, removing the friend relationship between the BIO methods and the TLS session ensures that future developers use the public functions of the session rather than directly modifying the members of the class. Signed-off-by: Michael Boquard <[email protected]>
1 parent 59f73a6 commit a2a5da3

File tree

1 file changed

+69
-19
lines changed

1 file changed

+69
-19
lines changed

src/net/ossl.cc

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,7 @@ class session : public enable_shared_from_this<session>, public session_impl {
10071007
, _in_sem(1)
10081008
, _out_sem(1)
10091009
, _options(std::move(options))
1010+
, _output_in_progress(false)
10101011
, _output_pending(make_ready_future<>())
10111012
, _ctx(make_ssl_context(t))
10121013
, _ssl([this]() {
@@ -1065,7 +1066,7 @@ class session : public enable_shared_from_this<session>, public session_impl {
10651066
: session(t, std::move(creds), net::get_impl::get(std::move(sock)), options) {}
10661067

10671068
~session() {
1068-
SEASTAR_ASSERT(_output_pending.available());
1069+
SEASTAR_ASSERT(output_available());
10691070
}
10701071

10711072
const char * get_type_string() const {
@@ -1081,6 +1082,8 @@ class session : public enable_shared_from_this<session>, public session_impl {
10811082
tls_log.debug("{} wait_for_output error: {}", *this, ep);
10821083
_error = ep;
10831084
return make_exception_future(ep);
1085+
}).finally([this] {
1086+
_output_in_progress = false;
10841087
});
10851088
}
10861089

@@ -1166,7 +1169,16 @@ class session : public enable_shared_from_this<session>, public session_impl {
11661169
// any unprocessed part of the packet is returned.
11671170
future<> do_put(net::packet p) {
11681171
tls_log.trace("{} do_put", *this);
1169-
SEASTAR_ASSERT(_output_pending.available());
1172+
1173+
// the put path is protected from concurrent calls by the _out_sem semaphore,
1174+
// however it is possible via the read (get()) path that a renegotiation may
1175+
// occur, putting an outstanding write on the output path. Originally,
1176+
// there was an assert to ensure that _output_pending was available. This assert
1177+
// would correctly serve the purpose that no writes occurred outside of the protection
1178+
// of the _out_sem. However, since writes from OpenSSL can occurr outside of this semaphore,
1179+
// we will attempt to perform a write even if there is an outstanding write pending.
1180+
// This will result in SSL_write_ex failing with SSL_WANT_WRITE as the error code.
1181+
// This will force a wait_for_output to occur.
11701182
return do_with(std::move(p),
11711183
[this](net::packet& p) {
11721184
// This do_until runs until either a renegotiation occurs or the packet is empty
@@ -1655,7 +1667,9 @@ class session : public enable_shared_from_this<session>, public session_impl {
16551667
// handshake aqcuire, because in worst case, we get here while a reader is attempting
16561668
// re-handshake.
16571669
return with_semaphore(_in_sem, 1, [this] {
1658-
return with_semaphore(_out_sem, 1, [] { });
1670+
return with_semaphore(_out_sem, 1, [this] {
1671+
_output_in_progress = false;
1672+
});
16591673
});
16601674
}).handle_exception([me = shared_from_this()](std::exception_ptr){
16611675
}).discard_result());
@@ -1788,6 +1802,38 @@ class session : public enable_shared_from_this<session>, public session_impl {
17881802
return _remote_address;
17891803
}
17901804

1805+
future<>& output_pending() {
1806+
return _output_pending;
1807+
}
1808+
1809+
bool output_available() {
1810+
// the wait_for_output method exchanges _output_pending for a ready
1811+
// future and then awaits for the output to complete. Checking that
1812+
// _output_pending is not enough to ensure that the output has completed.
1813+
// The purpose of the output_in_progress flag is to away that that
1814+
// future has resolved before permitting another write to be enqueued.
1815+
return !_output_in_progress && _output_pending.available();
1816+
}
1817+
1818+
void assign_output_pending(future<> f) {
1819+
SEASTAR_ASSERT(!_output_in_progress);
1820+
SEASTAR_ASSERT(_output_pending.available());
1821+
_output_in_progress = true;
1822+
_output_pending = std::move(f);
1823+
}
1824+
1825+
void assign_output_error(std::exception_ptr ep) {
1826+
_output_pending = make_exception_future<>(ep);
1827+
}
1828+
1829+
data_sink& out() {
1830+
return _out;
1831+
}
1832+
1833+
buf_type& input() {
1834+
return _input;
1835+
}
1836+
17911837
private:
17921838
std::vector<subject_alt_name> do_get_alt_name_information(const x509_ptr &peer_cert,
17931839
const std::unordered_set<subject_alt_name_type> &types) const {
@@ -2152,6 +2198,7 @@ class session : public enable_shared_from_this<session>, public session_impl {
21522198
semaphore _out_sem;
21532199
tls_options _options;
21542200

2201+
bool _output_in_progress{false};
21552202
future<> _output_pending;
21562203
buf_type _input;
21572204
// ALPN protocols in OPENSSL format
@@ -2165,9 +2212,9 @@ class session : public enable_shared_from_this<session>, public session_impl {
21652212
bool _eof = false;
21662213
bool _shutdown = false;
21672214

2168-
friend int bio_write_ex(BIO* b, const char * data, size_t dlen, size_t * written);
2169-
friend int bio_read_ex(BIO* b, char * data, size_t dlen, size_t *readbytes);
2170-
friend long bio_ctrl(BIO * b, int ctrl, long num, void * data);
2215+
// friend int bio_write_ex(BIO* b, const char * data, size_t dlen, size_t * written);
2216+
// friend int bio_read_ex(BIO* b, char * data, size_t dlen, size_t *readbytes);
2217+
// friend long bio_ctrl(BIO * b, int ctrl, long num, void * data);
21712218
friend int session_ticket_cb(SSL*, unsigned char[16], unsigned char[EVP_MAX_IV_LENGTH],
21722219
EVP_CIPHER_CTX*, EVP_MAC_CTX*, int);
21732220
};
@@ -2266,9 +2313,9 @@ long bio_ctrl(BIO * b, int ctrl, long num, void * data) {
22662313
case BIO_CTRL_EOF:
22672314
return BIO_test_flags(b, BIO_FLAGS_IN_EOF) != 0;
22682315
case BIO_CTRL_PENDING:
2269-
return static_cast<long>(session->_input.size());
2316+
return static_cast<long>(session->input().size());
22702317
case BIO_CTRL_WPENDING:
2271-
return session->_output_pending.available() ? 0 : 1;
2318+
return session->output_available() ? 0 : 1;
22722319
default:
22732320
return 0;
22742321
}
@@ -2294,7 +2341,7 @@ int bio_write_ex(BIO* b, const char * data, size_t dlen, size_t * written) {
22942341
tls_log.trace("{} bio_write_ex: dlen {}", *session, dlen);
22952342
BIO_clear_retry_flags(b);
22962343

2297-
if (!session->_output_pending.available()) {
2344+
if (!session->output_available()) {
22982345
tls_log.trace("{} bio_write_ex: nothing pending in output", *session);
22992346
BIO_set_retry_write(b);
23002347
return 0;
@@ -2303,17 +2350,20 @@ int bio_write_ex(BIO* b, const char * data, size_t dlen, size_t * written) {
23032350
try {
23042351
size_t n;
23052352

2306-
if (!session->_output_pending.failed()) {
2353+
if (!session->output_pending().failed()) {
23072354
scattered_message<char> msg;
23082355
msg.append(std::string_view(data, dlen));
23092356
n = msg.size();
2310-
session->_output_pending = session->_out.put(std::move(msg).release());
2357+
// Important here to use the assign_output_pending function. This ensures
2358+
// that there are no outstanding writes in progress and sets the
2359+
// output_pending flag
2360+
session->assign_output_pending(session->out().put(std::move(msg).release()));
23112361
tls_log.trace("{} bio_write_ex: Appended {} bytes to output pending", *session, n);
23122362
}
23132363

2314-
if (session->_output_pending.failed()) {
2364+
if (session->output_pending().failed()) {
23152365
tls_log.debug("{} bio_write_ex: output pending has error", *session);
2316-
std::rethrow_exception(session->_output_pending.get_exception());
2366+
std::rethrow_exception(session->output_pending().get_exception());
23172367
}
23182368

23192369
if (written != nullptr) {
@@ -2324,11 +2374,11 @@ int bio_write_ex(BIO* b, const char * data, size_t dlen, size_t * written) {
23242374
} catch(const std::system_error & e) {
23252375
tls_log.debug("{} bio_write_ex: system error occurred: {}", *session, e.what());
23262376
ERR_raise_data(ERR_LIB_SYS, e.code().value(), e.what());
2327-
session->_output_pending = make_exception_future<>(std::current_exception());
2377+
session->assign_output_error(std::current_exception());
23282378
} catch(...) {
23292379
tls_log.debug("{} bio_write_ex: unknown error occurred", *session);
23302380
ERR_raise(ERR_LIB_SYS, EIO);
2331-
session->_output_pending = make_exception_future<>(std::current_exception());
2381+
session->assign_output_error(std::current_exception());
23322382
}
23332383

23342384
return 0;
@@ -2350,15 +2400,15 @@ int bio_read_ex(BIO* b, char * data, size_t dlen, size_t *readbytes) {
23502400
return 0;
23512401
}
23522402

2353-
if (session->_input.empty()) {
2403+
if (session->input().empty()) {
23542404
tls_log.trace("{} bio_read_ex: input empty", *session);
23552405
BIO_set_retry_read(b);
23562406
return 0;
23572407
}
23582408

2359-
auto n = std::min(dlen, session->_input.size());
2360-
memcpy(data, session->_input.get(), n);
2361-
session->_input.trim_front(n);
2409+
auto n = std::min(dlen, session->input().size());
2410+
memcpy(data, session->input().get(), n);
2411+
session->input().trim_front(n);
23622412
if (readbytes != nullptr) {
23632413
*readbytes = n;
23642414
}

0 commit comments

Comments
 (0)