Fix data loss when POLLHUP races with unread kernel data#23
Open
SoloJacobs wants to merge 2 commits into
Open
Conversation
When a backend (unix-socket) peer closes its connection, Linux can report POLLHUP without simultaneously reporting POLLIN, even though there is still data waiting in the kernel receive buffer. The remaining data in the buffer should be transmitted, quoting from https://man7.org/linux/man-pages/man2/poll.2.html > POLLHUP > > Hang up (only returned in revents; ignored in events). Note that when > reading from a channel such as a pipe or a stream socket, this event > merely indicates that the peer closed its end of the channel. Subsequent > reads from the channel will return 0 (end of file) only after all > outstanding data in the channel has been consumed. The already-correct "Read socket closed (read hangup)" path guards the same close decision with an ioctlsocket(FIONREAD) check. This patch simply applies the guard to s_poll_hup().
Previously, `stunnel` would simply abandon the data in the read buffer, once a `POLLHUP` signal is received. With `0001-fix-hup-race-data-loss.patch` we attempt to send the remaining data. In particular, we require for the `transfer()` loop to make progress, even if the socket is closed. The following issue could occur: If the backend has closed the connection while both the read and TLS send buffers are full, `stunnel` would still register the backend socket with `poll()`. `poll()` returns immediately on every iteration due to `POLLHUP`. This would exhausts the max iteration number in the `transfer()` loop. Subsequently, the data in the read buffer would be abandoned. `s_poll_add` opts into `POLLRDHUP` by explicitly setting it in `events` when the buffer has room to receive data. This way TCP sockets avoid the busy loop and the associated data loss. `POLLHUP` has no equivalent mechanism: https://man7.org/linux/man-pages/man2/poll.2.html > The bits returned in revents can include any of those specified in > events, or one of the values POLLERR, POLLHUP, or POLLNVAL. The fix: We omit the call to `s_poll_add` completely if the read buffer is full. This way the `transfer()` loop will block until the TLS send buffer has room, allowing data to drain before polling the backend again. Any socket errors on the backend fd are detected on the next iteration once sock_ptr has drained below BUFFSIZE and the fd is polled again.
Author
|
So, I found the previous patch did not fix all the instances of truncation. Thus, I have added another patch. With this one our live instance no longer looses any data. Of course, there might be more issues, which are just not able to reproduce. Kind regards |
CheckmkCI
pushed a commit
to Checkmk/checkmk
that referenced
this pull request
May 12, 2026
…esponses When a unix-socket backend (CMC) closes its connection immediately after writing the last chunk, the Linux kernel can report POLLHUP on the stunnel side before reporting POLLIN, even though data is still sitting in the socket receive buffer. stunnel then silently discarded the buffered data. The POLLRDHUP path already has a guard in place: ```c /* http://marc.info/?l=linux-man&m=128002066306087 */ /* readsocket() must be the last sock_rfd operation before FIONREAD */ if(sock_open_rd && s_poll_rdhup(c->fds, c->sock_rfd->fd) && (ioctlsocket(c->sock_rfd->fd, FIONREAD, &bytes) || !bytes)) { s_log(LOG_INFO, "Read socket closed (read hangup)"); sock_open_rd=0; } ``` This guard is the reason why this bug is specific to unix sockets: For a unix socket stunnel receives a POLLHUP signal rather than a POLLRDHUP signal. The patch simply copies that guard to the `POLLHUP` path. An upstream PR was opened: mtrojnar/stunnel#23 stunnel was updated to the latest version 5.78. SUP-26589 Change-Id: I5bec48e79a3bd938dd8941591bac3b64eea8e319
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Some of our customers reported that responses of our UNIX socket based back-ends were truncated. After some investigation, I found that this issue could be fairly easily reproduced with the below AI generated python script.
I found some related bug reports:
However, the issue can still be reproduced in stunnel 5.78.
For the implementation, I simply copied this implementation from
src/client.c:Now my knowledge of c is somewhat limited, I only have a surface level understanding of read loop in src/client.c . The patch should thus be reviewed with caution.