Subject: [Boost-bugs] [Boost C++ Libraries] #13319: Boost.Asio.SSL write_some cause 'decryption failed or bad record mac' on large (~1MB) transmissions
From: Boost C++ Libraries (noreply_at_[hidden])
Date: 2017-12-04 09:38:35
#13319: Boost.Asio.SSL write_some cause 'decryption failed or bad record mac' on
large (~1MB) transmissions
-----------------------------------------------+---------------------------
Reporter: Andrey Borisov <andrey.borisov@â¦> | Owner:
| chris_kohlhoff
Type: Bugs | Status: new
Milestone: To Be Determined | Component: asio
Version: Boost 1.65.0 | Severity: Problem
Keywords: asio ssl write_some |
-----------------------------------------------+---------------------------
When we send large amount of data (1MB) using boost::asio::ssl::stream the
boost closes the connection after some transmission and reports encryption
error : 'decryption failed or bad record mac'.
The reason of it is a write_some function.
Shortly it uses boost::asio::write to send encrypted data to TCP socket
(boost_1_57_0\boost\asio\ssl\detail\io.hpp:60, see details below), but the
amount of really sent data is ignored. Instead of it, the amount of
handled by OpenSSL user application buffer is returned to the user
application. In case of 'would block' operation the amount of really sent
data is less than reported to user application.
Details:
If the socket is unblocking, the write_some has to try to send something
over the socket and has to return the amount of the data it is able to
send.
The application has to retry next time for the rest of the output data,
taking in account the amount of just sent data.
The SSL implementation uses paired BIO of OpenSSL to allow sending
encrypted data not directly to a TCP socket but in a memory buffers.
The Boost uses this buffers to integrate SSL in its engine implementing
asynchronous socket operation.
In other words, boost internally works with TCP sockets using its
mechanisms of direct or asynchronous data operation getting data from
OpenSSL buffer.
For the write operation the sequence is the following:
1. User application calls boost::asio::ssl::stream::write_some
operation and gives it the buffer with initial data.
2. This operation calls the engine detail::io function. This function
has to encrypt data using OpenSSL, read the encrypted data from the
coupled OpenSSL BIO buffer and then send it out using TCP socket.
3. First the engine io function calls the detail::write_op. This
operation (in subroutines) calls the ::SSL_write. This function uses a BIO
buffer to store encrypted data and returns the amount of handled initial
data. The current buffer size is 17KB. In case of large outgoing message,
the size will be about 16+KB.
4. The size of the handled initial data is stored to bytes_transfered
and then is used as a return value of sent amount to the user application.
5. After this operation the SSL_get_error returns what OpenSSL wants
â send the encrypted data through real socket or read raw data from the
socket.
6. In our case normally the Open SSL just want to send the portion of
encrypted data.
7. The detail::io execute the case engine::want_output:. It calls
boost::asio::write(next_layer,
core.engine_.get_output(core.output_buffer_), ec);
8. This function read the encrypted data from OpenSSL buffer and
transmit it using usual boost write operation.
9. The problem is that the return of this function is not checked.
The amount of the send data by this function can be less than required
(16KB). But the user application is notified that that write_some function
have sent 16+K.
10. This leads to the unsynchronized state of SSL on both sides. The
sender consider it sends +16KB, but receiver has gotten say only 4KB.
Other data are lost.
11. The user application makes next send skipping the 16KB. And after
some send operation OpenSSL recognize the error state. It reports the
error and boost closes the socket.
12. The reason of why write operation can send not the entire
encrypted portion (16KB) is the following:
a. It tries sending the full buffer, but in case of large data stream
the corresponding socket can get stacked, and the output buffer can
overflow.
b. In this case the socket operation has the following options
i. Block in send operation in case of blocking socked
ii. Return âwould blockâ error in case of nonblocking socket.
c. In our case we have nonblocking socket and write operation stops
execution in this case and returns the amount of really sent data. But
this value is just ignored.
Workaround: don't use write_some with ssl.
-- Ticket URL: <https://svn.boost.org/trac10/boost/ticket/13319> Boost C++ Libraries <http://www.boost.org/> Boost provides free peer-reviewed portable C++ source libraries.
This archive was generated by hypermail 2.1.7 : 2017-12-04 09:44:58 UTC