Jens Geyer created THRIFT-6214:
----------------------------------

             Summary: C++: TSSLSocketMatchNameTest is killed by SIGPIPE in 
about a third of runs
                 Key: THRIFT-6214
                 URL: https://issues.apache.org/jira/browse/THRIFT-6214
             Project: Thrift
          Issue Type: Bug
          Components: C++ - Library
            Reporter: Jens Geyer


h2. What happens

{{TSSLSocketMatchNameTest}} is killed by {{SIGPIPE}} on some runs:

{noformat}
47/58 Test #47: TSSLSocketMatchNameTest ...............SIGPIPE***Exception:   
0.17 sec
{noformat}

Run directly, the binary exits with status 141 and prints nothing, because the 
signal ends it before Boost.Test gets to report. Built from master it failed 3 
of 10 consecutive runs; the others pass.

h2. Where it comes from

Under gdb the signal arrives in the server thread of the negative handshake 
case (backtrace abridged):

{noformat}
Entering test case "common_name_is_not_consulted_when_a_dns_san_is_present"
Thread 3 "TSSLSocketMatch" received signal SIGPIPE, Broken pipe.
#5  BIO_write () from /lib/x86_64-linux-gnu/libcrypto.so.3
#7  BIO_ctrl () from /lib/x86_64-linux-gnu/libcrypto.so.3
#8  ?? () from /lib/x86_64-linux-gnu/libssl.so.3
#10 apache::thrift::transport::TSSLSocket::initializeHandshake() at 
lib/cpp/src/thrift/transport/TSSLSocket.cpp:650
#11 apache::thrift::transport::TSSLSocket::write(buf="OK", len=2) at 
lib/cpp/src/thrift/transport/TSSLSocket.cpp:509
#13 TSSLSocketMatchNameTest::(anonymous namespace)::OneShotServer::run() at 
lib/cpp/test/TSSLSocketMatchNameTest.cpp:195
{noformat}

That case exists to have the client refuse the server's certificate, and it 
does: the client gives up and closes the connection while 
{{OneShotServer::run()}} is still inside {{SSL_accept()}}, which its 
{{write()}} started. {{TSSLSocket}} hands the descriptor to OpenSSL 
({{SSL_set_fd}}), and OpenSSL's socket BIO writes without {{MSG_NOSIGNAL}}. 
When the server's next handshake write reaches a connection the client has 
already torn down, it raises {{SIGPIPE}}, and since nothing in this test module 
ignores the signal, the process dies. When the write gets out first, the 
handshake fails on the following read instead, and {{run()}} catches that as 
intended -- hence the flakiness.

Every other test that drives TLS connections ignores the signal for exactly 
this reason -- {{SecurityTest}}, {{SecurityFromBufferTest}}, 
{{TNonblockingSSLServerTest}} and {{TSSLSocketInterruptTest}} in 
{{lib/cpp/test}}, and {{test/cpp/src/TestServer.cpp}}:

{code:cpp}
// OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
// disconnected can cause a SIGPIPE signal...
signal(SIGPIPE, SIG_IGN);
{code}

{{TSSLSocketMatchNameTest}} does not. It only called 
{{DefaultClientAccessManager::verify()}} until cd199715b added the handshake 
cases, and the signal handling did not come along with them.

h2. Suggested fix

Ignore {{SIGPIPE}} in {{TSSLSocketMatchNameTest}} the way {{SecurityTest.cpp}} 
does, with a {{BOOST_GLOBAL_FIXTURE}}. The server's write then fails with 
{{EPIPE}}, {{SSL_accept()}} returns an error, and {{OneShotServer::run()}} 
swallows the resulting exception, which it already expects in this case.

h2. Seen with

Current master -- built from 2f75006ca; {{lib/cpp}} is unchanged up to 
e1176aff9 -- in the {{thrift:jammy}} build image: Ubuntu 22.04, OpenSSL 3.0.2, 
GCC 11.4.0, Boost 1.74, CMake Debug build.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to