Skip to content

Socket::write sends without MSG_NOSIGNAL, so a peer that has gone away kills the process with SIGPIPE from inside the pool clean-up #16

Description

@yspbwx2010

Socket::write is a bare ::send(fd_, buf, len, 0) (socket.cppm:176-179), and nothing in the package sets MSG_NOSIGNAL, SO_NOSIGPIPE or a SIGPIPE disposition. A write to a socket whose peer has gone away therefore raises SIGPIPE, and a program that has not disarmed the signal, which is the default, is killed rather than told.

Where it was measured (the peer-close case of #15, same scripts): one HttpClient, the server answers a request and then closes the connection; the client keeps the socket in the pool because is_valid() only tests the descriptor. On the next request the write goes out without error, read_line at http.cppm:447 returns empty, and the "No response" path erases the pool entry at :449. That destroys the pooled TlsSocket; its destructor calls close() (tls.cppm:151), which sends a TLS close-notify through bio_send (tls.cppm:48) into Socket::write on a socket that has received the peer's RST. The process ends with exit status 141. gdb: __libc_send under Socket::write (socket.cppm:178) under bio_send (tls.cppm:48) under mbedtls_ssl_flush_output, mbedtls_ssl_write_record, mbedtls_ssl_send_alert_message, mbedtls_ssl_close_notify, under TlsSocket::close (tls.cppm:151) under ~TlsSocket (tls.cppm:70) under send_impl (http.cppm:449). With SIGPIPE ignored the same run returns statusCode=0 statusText="No response", which is what the code intends.

Why this is separate from #15: fixing the pool bookkeeping there does not remove this path. Any peer that closes an idle keep-alive connection, which servers do after a few seconds to a few minutes, leaves the client's pooled socket in the same state, and the next request through it takes the same route to :449. I have not measured the idle-timeout case itself, only the explicit close in #15; the socket state the two leave behind is the same.

What the fix could be: on Linux, ::send(fd_, buf, len, MSG_NOSIGNAL); on macOS setsockopt(fd_, SOL_SOCKET, SO_NOSIGPIPE, ...) after connecting, since MSG_NOSIGNAL is not available there; Windows has no SIGPIPE. With that in place bio_send returns MBEDTLS_ERR_NET_SEND_FAILED on the dead socket and the close-notify simply fails, which TlsSocket::close already tolerates. Ignoring SIGPIPE process-wide also works, and is what I am doing on my side, but that is a decision for the program rather than something a library that calls send should require.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions