Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/openssl/src/openssl_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,11 @@ struct openssl_stream_impl_
{
if(ec == make_error_code(capy::error::eof))
{
// Check if we got a proper shutdown
// Check if we got a proper TLS shutdown
if(SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN)
ec = make_error_code(capy::error::eof);
else
ec = make_error_code(capy::error::stream_truncated);
}
goto done;
}
Expand All @@ -373,7 +375,7 @@ struct openssl_stream_impl_
{
unsigned long ssl_err = ERR_get_error();
if(ssl_err == 0)
ec = make_error_code(capy::error::eof);
ec = make_error_code(capy::error::stream_truncated);
else
ec = system::error_code(
static_cast<int>(ssl_err), system::system_category());
Expand Down
17 changes: 16 additions & 1 deletion src/wolfssl/src/wolfssl_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,22 @@ struct wolfssl_stream_impl_
if(read_in_pos_ == read_in_len_) { read_in_pos_ = 0; read_in_len_ = 0; }
capy::mutable_buffer buf(read_in_buf_.data() + read_in_len_, read_in_buf_.size() - read_in_len_);
auto [rec, rn] = co_await do_underlying_read(buf);
if(rec) { ec = rec; goto done; }
if(rec)
{
if(rec == make_error_code(capy::error::eof))
{
// Check if we got a proper TLS shutdown
if(wolfSSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN)
ec = make_error_code(capy::error::eof);
else
ec = make_error_code(capy::error::stream_truncated);
}
else
{
ec = rec;
}
goto done;
}
Comment on lines +424 to +439
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the wolfssl_stream.cpp file
find . -name "wolfssl_stream.cpp" -type f

Repository: cppalliance/corosio

Length of output: 134


🏁 Script executed:

# Check the file size and content around lines 424-439
wc -l $(find . -name "wolfssl_stream.cpp" -type f)

Repository: cppalliance/corosio

Length of output: 158


🏁 Script executed:

# Examine the specific lines in question
sed -n '420,445p' $(find . -name "wolfssl_stream.cpp" -type f 2>/dev/null | head -1)

Repository: cppalliance/corosio

Length of output: 1370


🏁 Script executed:

# Search for SSL_RECEIVED_SHUTDOWN in WolfSSL headers
find . -name "*.h" -path "*/wolfssl/*" -type f | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look for SSL_RECEIVED_SHUTDOWN definition
rg "SSL_RECEIVED_SHUTDOWN" --type h

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check WolfSSL version and compatibility layer
rg "include.*wolf" $(find . -name "wolfssl_stream.cpp" -type f 2>/dev/null | head -1) | head -20

Repository: cppalliance/corosio

Length of output: 195


🏁 Script executed:

# Search for SSL_RECEIVED_SHUTDOWN definition in WolfSSL headers
rg "SSL_RECEIVED_SHUTDOWN" . --type h

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check WolfSSL version or configuration
rg "WOLFSSL_VERSION\|OPENSSL_COMPATIBLE" . --type h | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look for wolfssl headers location
find . -path "*/wolfssl/*.h" -type f | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check if SSL_RECEIVED_SHUTDOWN is defined in the included headers
cat $(find . -path "*/wolfssl/ssl.h" -type f 2>/dev/null | head -1) | grep -A 2 -B 2 "SSL_RECEIVED_SHUTDOWN" || echo "Not found in ssl.h"

Repository: cppalliance/corosio

Length of output: 81


🏁 Script executed:

# Search more broadly for SSL_RECEIVED_SHUTDOWN or similar constants
rg "RECEIVED_SHUTDOWN|SENT_SHUTDOWN" .

Repository: cppalliance/corosio

Length of output: 306


🏁 Script executed:

# Check if SSL_RECEIVED_SHUTDOWN is defined anywhere in the codebase
rg "define.*SSL_RECEIVED_SHUTDOWN" .

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look for OpenSSL compatibility mode definitions
rg "OPENSSL_EXTRA\|OPENSSL_ALL\|WOLFSSL_OPENSSL_COMPAT" . --type h | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check the full context around the SSL_RECEIVED_SHUTDOWN usage in both files
sed -n '415,450p' ./src/wolfssl/src/wolfssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 1834


🏁 Script executed:

# Check the OpenSSL version for comparison
sed -n '415,450p' ./src/openssl/src/openssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 1305


🏁 Script executed:

# Look at includes in the test file to understand how this should work
head -50 ./test/unit/tls/wolfssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 1769


🏁 Script executed:

# Search for any shutdown-related constants in WolfSSL
rg "SHUTDOWN\|shutdown" . --type h | grep -i "define\|enum\|const" | head -30

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check WolfSSL documentation or constants in available headers
find . -path "*/wolfssl/*.h" -type f -exec grep -l "shutdown" {} \;

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look for how WolfSSL defines shutdown status
rg "wolfSSL_get_shutdown\|SSL_get_shutdown" . --type h

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check if there's a compatibility shim or definition we need to add
rg "SSL_RECEIVED_SHUTDOWN\|SSL_SENT_SHUTDOWN" . --type cpp | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look at compiler configuration or CMake to see how WolfSSL is linked
find . -name "CMakeLists.txt" -o -name "*.cmake" | xargs grep -l "wolfssl\|WolfSSL" 2>/dev/null | head -5

Repository: cppalliance/corosio

Length of output: 195


🏁 Script executed:

# Check if SSL_RECEIVED_SHUTDOWN might be from OpenSSL headers that WolfSSL redefines
rg "include.*openssl\|include.*wolfssl" ./src/wolfssl/src/wolfssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check the CMakeLists.txt for WolfSSL configuration
cat ./CMakeLists.txt | grep -A 20 -B 5 "wolfssl\|WolfSSL" | head -50

Repository: cppalliance/corosio

Length of output: 2503


🏁 Script executed:

# Check the FindWolfSSL.cmake file
cat ./cmake/FindWolfSSL.cmake | head -100

Repository: cppalliance/corosio

Length of output: 957


🏁 Script executed:

# Look for any defines or compiler flags that might define SSL_RECEIVED_SHUTDOWN
rg "SSL_RECEIVED_SHUTDOWN\|OPENSSL_EXTRA\|OPENSSL_ALL" ./CMakeLists.txt ./cmake/

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check if wolfssl/options.h sets any compatibility flags
cat $(find . -name "options.h" -path "*/wolfssl/*" 2>/dev/null | head -1) | head -100

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Search for macro definitions or missing constants related to shutdown
rg "#define.*SHUTDOWN|const.*SHUTDOWN|enum.*SHUTDOWN" . --type cpp | head -20

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look at the complete includes and early definitions in wolfssl_stream.cpp
head -100 ./src/wolfssl/src/wolfssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 3802


🏁 Script executed:

# Search for any conditional compilation or runtime checks that might define SSL_RECEIVED_SHUTDOWN
rg "#ifdef.*OPENSSL|#if.*OPENSSL" . --type cpp --type h | head -20

Repository: cppalliance/corosio

Length of output: 522


🏁 Script executed:

# Check if there's a compatibility header that might define this
find . -name "*.hpp" -o -name "*.h" | xargs grep -l "SSL_RECEIVED_SHUTDOWN" 2>/dev/null

Repository: cppalliance/corosio

Length of output: 47


🏁 Script executed:

# Try a web search to verify SSL_RECEIVED_SHUTDOWN in WolfSSL

Repository: cppalliance/corosio

Length of output: 45


🌐 Web query:

SSL_RECEIVED_SHUTDOWN WolfSSL OpenSSL constant definition

💡 Result:

Short answer:

  • OpenSSL: SSL_SENT_SHUTDOWN = 0x01 (value 1), SSL_RECEIVED_SHUTDOWN = 0x02 (value 2); they are shutdown-state bitflags (sent = close_notify sent, received = close_notify received). [1]

  • wolfSSL: wolfSSL_get_shutdown() returns 1 for SSL_SENT_SHUTDOWN and 2 for SSL_RECEIVED_SHUTDOWN (same numeric meanings). [2]

Sources:
[1] OpenSSL manpage SSL_set_shutdown / SSL_shutdown.
[2] wolfSSL documentation (wolfSSL_shutdown / wolfSSL_get_shutdown).


🏁 Script executed:

# Check what constants WolfSSL actually provides for shutdown status
rg "get_shutdown\|wolfSSL_get_shutdown" . --type cpp -B 3 -A 3

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look for any `#define` or constant that might be a substitute
rg "define.*SHUTDOWN|SHUTDOWN.*=" . --type cpp --type h | grep -v "Binary"

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check if SSL_RECEIVED_SHUTDOWN is supposed to come from wolfssl/ssl.h
# Let's verify by looking at what WolfSSL actually defines
rg "SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN" . --type cpp

Repository: cppalliance/corosio

Length of output: 306


🏁 Script executed:

# Check if it's conditionally defined or if there's a missing include
rg "^\s*#define\s+SSL_RECEIVED_SHUTDOWN|^\s*#define\s+SSL_SENT_SHUTDOWN" . --type cpp --type h

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Look at what wolfssl/ssl.h actually provides in the system
# First check if wolfssl/ssl.h from the include path defines these
python3 << 'EOF'
import subprocess
import re

# Search for any file that might define these constants
result = subprocess.run(['rg', '-l', 'SSL_RECEIVED_SHUTDOWN', '.'], 
                       capture_output=True, text=True)
if result.stdout.strip():
    for line in result.stdout.strip().split('\n'):
        print(f"Found in: {line}")
else:
    print("SSL_RECEIVED_SHUTDOWN not found in codebase")

# Check if it appears in comments or documentation
result = subprocess.run(['rg', 'SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN', '.', '--type', 'cpp'],
                       capture_output=True, text=True)
print("\nAll uses of SSL_*_SHUTDOWN:")
print(result.stdout)
EOF

Repository: cppalliance/corosio

Length of output: 149


🏁 Script executed:

# Check OpenSSL stream implementation to see how it's used there
sed -n '410,450p' ./src/openssl/src/openssl_stream.cpp

Repository: cppalliance/corosio

Length of output: 1484


🏁 Script executed:

# Search in wolfSSL documentation or comments what value to use
rg "0x02\|shutdown.*2\|RECEIVED" ./src/wolfssl/src/wolfssl_stream.cpp -B 2 -A 2

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check if there's a compatibility layer or definition we're missing
grep -r "SSL_RECEIVED_SHUTDOWN\|SSL_SENT_SHUTDOWN" include/ src/ 2>/dev/null || echo "Not found in include/src"

Repository: cppalliance/corosio

Length of output: 302


SSL_RECEIVED_SHUTDOWN may not be available in all WolfSSL builds.

The code uses SSL_RECEIVED_SHUTDOWN without verifying it's defined. While WolfSSL does support this constant when built with OpenSSL compatibility (--enable-opensslextra or --enable-opensslall), many standard distributions omit these flags. This will cause compilation errors on incompatible WolfSSL installations.

Either:

  • Add a CMake check in FindWolfSSL.cmake to verify OpenSSL compatibility mode is enabled, or
  • Provide a fallback definition (e.g., #if !defined(SSL_RECEIVED_SHUTDOWN) #defineSSL_RECEIVED_SHUTDOWN 0x02#endif``)
🤖 Prompt for AI Agents
In `@src/wolfssl/src/wolfssl_stream.cpp` around lines 424 - 439, The code uses
SSL_RECEIVED_SHUTDOWN in the wolfSSL_stream.cpp path that checks
wolfSSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN, but that constant may be
missing in non-OpenSSL-compat WolfSSL builds; update the project to handle both
cases by either adding a CMake/FindWolfSSL check to assert OpenSSL-compat mode
is present or add a local fallback macro so compilation succeeds (e.g., detect
if SSL_RECEIVED_SHUTDOWN is undefined and define a sensible fallback) and keep
the existing logic that calls wolfSSL_get_shutdown(ssl_) unchanged; reference
SSL_RECEIVED_SHUTDOWN and wolfSSL_get_shutdown(ssl_) when implementing the check
or defining the fallback.

read_in_len_ += rn;
}
else if(err == WOLFSSL_ERROR_WANT_WRITE)
Expand Down
32 changes: 32 additions & 0 deletions test/unit/tls/openssl_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,45 @@ struct openssl_stream_test
ioc.restart();
}
}

void
testTlsShutdown()
{
using namespace tls::test;

for( auto mode : { context_mode::shared_cert,
context_mode::separate_cert } )
{
io_context ioc;
auto [client_ctx, server_ctx] = make_contexts( mode );
run_tls_shutdown_test( ioc, client_ctx, server_ctx,
make_stream, make_stream );
}
}

void
testStreamTruncated()
{
using namespace tls::test;

for( auto mode : { context_mode::shared_cert,
context_mode::separate_cert } )
{
io_context ioc;
auto [client_ctx, server_ctx] = make_contexts( mode );
run_tls_truncation_test( ioc, client_ctx, server_ctx,
make_stream, make_stream );
}
}
#endif

void
run()
{
#ifdef BOOST_COROSIO_HAS_OPENSSL
testSuccessCases();
testTlsShutdown();
testStreamTruncated();
// Failure tests disabled: socket cancellation doesn't propagate to
// TLS handshake operations, causing hangs when one side fails.
// testFailureCases();
Expand Down
213 changes: 213 additions & 0 deletions test/unit/tls/test_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <boost/corosio/tls/tls_stream.hpp>
#include <boost/corosio/test/socket_pair.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/cond.hpp>
#include <boost/capy/ex/run_async.hpp>
#include <boost/capy/task.hpp>

Expand Down Expand Up @@ -446,6 +447,218 @@ run_tls_test_fail(
s2.close();
}

/** Run a TLS shutdown test with graceful close_notify.

Tests that one side can initiate TLS shutdown (sends close_notify)
and the other side receives EOF. Uses unidirectional shutdown to
avoid deadlock in single-threaded io_context.

Note: TLS shutdown in a single-threaded context can deadlock when both
sides wait for each other. We use a timeout to detect and recover from
potential deadlocks.

@param ioc The io_context to use
@param client_ctx TLS context for the client
@param server_ctx TLS context for the server
@param make_client Factory: (io_stream&, context) -> TLS stream
@param make_server Factory: (io_stream&, context) -> TLS stream
*/
template<typename ClientStreamFactory, typename ServerStreamFactory>
void
run_tls_shutdown_test(
io_context& ioc,
context client_ctx,
context server_ctx,
ClientStreamFactory make_client,
ServerStreamFactory make_server )
{
auto [s1, s2] = corosio::test::make_socket_pair( ioc );

auto client = make_client( s1, client_ctx );
auto server = make_server( s2, server_ctx );

// Handshake phase
auto client_hs = [&client]() -> capy::task<>
{
auto [ec] = co_await client.handshake( tls_stream::client );
BOOST_TEST( !ec );
};

auto server_hs = [&server]() -> capy::task<>
{
auto [ec] = co_await server.handshake( tls_stream::server );
BOOST_TEST( !ec );
};

capy::run_async( ioc.get_executor() )( client_hs() );
capy::run_async( ioc.get_executor() )( server_hs() );

ioc.run();
ioc.restart();

// Data transfer phase
auto transfer_task = [&client, &server]() -> capy::task<>
{
co_await test_stream( client, server );
};
capy::run_async( ioc.get_executor() )( transfer_task() );

ioc.run();
ioc.restart();

// Shutdown phase with timeout protection
bool shutdown_done = false;
bool read_done = false;

auto client_shutdown = [&client, &shutdown_done]() -> capy::task<>
{
auto [ec] = co_await client.shutdown();
shutdown_done = true;
// Shutdown may return success, canceled, or stream_truncated
BOOST_TEST( !ec || ec == capy::cond::stream_truncated ||
ec == capy::cond::canceled );
};

auto server_read_eof = [&server, &read_done]() -> capy::task<>
{
char buf[32];
auto [ec, n] = co_await server.read_some(
capy::mutable_buffer( buf, sizeof( buf ) ) );
read_done = true;
// Should get EOF, stream_truncated, or canceled
BOOST_TEST( ec == capy::cond::eof || ec == capy::cond::stream_truncated ||
ec == capy::cond::canceled );
};

// Timeout to prevent deadlock
timer timeout( ioc );
timeout.expires_after( std::chrono::milliseconds( 500 ) );
auto timeout_task = [&timeout, &s1, &s2, &shutdown_done, &read_done]() -> capy::task<>
{
(void)shutdown_done;
(void)read_done;
auto [ec] = co_await timeout.wait();
if( !ec )
{
// Timer expired - cancel pending operations (check if still open)
if( s1.is_open() ) { s1.cancel(); s1.close(); }
if( s2.is_open() ) { s2.cancel(); s2.close(); }
}
};

capy::run_async( ioc.get_executor() )( client_shutdown() );
capy::run_async( ioc.get_executor() )( server_read_eof() );
capy::run_async( ioc.get_executor() )( timeout_task() );

ioc.run();

timeout.cancel();
if( s1.is_open() ) s1.close();
if( s2.is_open() ) s2.close();
}

/** Run a test for stream truncation (socket close without TLS shutdown).

Tests that when one side closes the underlying socket without
performing TLS shutdown, the other side receives stream_truncated.

@param ioc The io_context to use
@param client_ctx TLS context for the client
@param server_ctx TLS context for the server
@param make_client Factory: (io_stream&, context) -> TLS stream
@param make_server Factory: (io_stream&, context) -> TLS stream
*/
template<typename ClientStreamFactory, typename ServerStreamFactory>
void
run_tls_truncation_test(
io_context& ioc,
context client_ctx,
context server_ctx,
ClientStreamFactory make_client,
ServerStreamFactory make_server )
{
auto [s1, s2] = corosio::test::make_socket_pair( ioc );

auto client = make_client( s1, client_ctx );
auto server = make_server( s2, server_ctx );

// Handshake phase
auto client_hs = [&client]() -> capy::task<>
{
auto [ec] = co_await client.handshake( tls_stream::client );
BOOST_TEST( !ec );
};

auto server_hs = [&server]() -> capy::task<>
{
auto [ec] = co_await server.handshake( tls_stream::server );
BOOST_TEST( !ec );
};

capy::run_async( ioc.get_executor() )( client_hs() );
capy::run_async( ioc.get_executor() )( server_hs() );

ioc.run();
ioc.restart();

// Data transfer phase
auto transfer_task = [&client, &server]() -> capy::task<>
{
co_await test_stream( client, server );
};
capy::run_async( ioc.get_executor() )( transfer_task() );

ioc.run();
ioc.restart();

// Truncation test with timeout protection
bool read_done = false;

auto client_close = [&s1]() -> capy::task<>
{
// Close underlying socket without TLS shutdown
s1.close();
co_return;
};

auto server_read_truncated = [&server, &read_done]() -> capy::task<>
{
char buf[32];
auto [ec, n] = co_await server.read_some(
capy::mutable_buffer( buf, sizeof( buf ) ) );
read_done = true;
// Should get stream_truncated, eof, or canceled
BOOST_TEST( ec == capy::cond::stream_truncated ||
ec == capy::cond::eof ||
ec == capy::cond::canceled );
};

// Timeout to prevent deadlock
timer timeout( ioc );
timeout.expires_after( std::chrono::milliseconds( 500 ) );
auto timeout_task = [&timeout, &s1, &s2, &read_done]() -> capy::task<>
{
(void)read_done;
auto [ec] = co_await timeout.wait();
if( !ec )
{
// Timer expired - cancel pending operations (check if still open)
if( s1.is_open() ) { s1.cancel(); s1.close(); }
if( s2.is_open() ) { s2.cancel(); s2.close(); }
}
};

capy::run_async( ioc.get_executor() )( client_close() );
capy::run_async( ioc.get_executor() )( server_read_truncated() );
capy::run_async( ioc.get_executor() )( timeout_task() );

ioc.run();

timeout.cancel();
if( s1.is_open() ) s1.close();
if( s2.is_open() ) s2.close();
}

} // namespace test
} // namespace tls
} // namespace corosio
Expand Down
32 changes: 32 additions & 0 deletions test/unit/tls/wolfssl_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,45 @@ struct wolfssl_stream_test
ioc.restart();
}
}

void
testTlsShutdown()
{
using namespace tls::test;

for( auto mode : { context_mode::shared_cert,
context_mode::separate_cert } )
{
io_context ioc;
auto [client_ctx, server_ctx] = make_contexts( mode );
run_tls_shutdown_test( ioc, client_ctx, server_ctx,
make_stream, make_stream );
}
}

void
testStreamTruncated()
{
using namespace tls::test;

for( auto mode : { context_mode::shared_cert,
context_mode::separate_cert } )
{
io_context ioc;
auto [client_ctx, server_ctx] = make_contexts( mode );
run_tls_truncation_test( ioc, client_ctx, server_ctx,
make_stream, make_stream );
}
}
#endif

void
run()
{
#ifdef BOOST_COROSIO_HAS_WOLFSSL
testSuccessCases();
testTlsShutdown();
testStreamTruncated();
// Failure tests disabled: socket cancellation doesn't propagate to
// TLS handshake operations, causing hangs when one side fails.
// testFailureCases();
Expand Down
Loading