macOS 15.7: IMAP TLS disconnect after login (SCM_RIGHTS fd lifetime)
Hello Dovecot developers,
I would like to report a reproducible IMAP disconnection after successful authentication when Dovecot runs on macOS. Our investigation points to the lifetime of a socketpair descriptor passed from the login process to the post-login process using SCM_RIGHTS.
Environment:
- macOS 15.7, build 24G222; Darwin 24.6.0; arm64.
- Failure observed with Dovecot 2.4.3 and unpatched 2.4.4.
- Currently running Dovecot 2.4.4 with the attached local workaround.
- OpenSSL 3.6.2 in the 2.4.4 build.
- The 2.4.4 build uses the poll I/O loop and kqueue filesystem notifications.
- SQL/MySQL passdb and userdb, virtual users, sdbox storage.
- Mail storage is on a locally attached APFS volume, not NFS.
Observed behavior:
- passdb/userdb resolves the expected user and home directory.
- Plain IMAP on localhost can list folders and read messages.
- With STARTTLS or implicit TLS, authentication succeeds, but the connection closes before subsequent IMAP commands can complete.
- The failure was investigated using Apple Mail and Python IMAP probes against both STARTTLS on port 143 and IMAPS on port 993.
Source review: On 2026-10-05 I inspected release 2.4.5 and main at commit d59e22f26bb975173755026baa48af88db1b44ef. In src/login-common/sasl-proxy.c, master_send_request() still calls login_client_request() and immediately closes the temporary descriptor when close_fd is true. The corresponding file was named sasl-server.c in 2.4.4.
src/lib-login/login-client.c passes the descriptor through fd_send() without retaining a duplicate until the backend replies. client_get_plaintext_fd() in client-common.c creates the socketpair used for TLS proxying. I did not find an equivalent descriptor-lifetime workaround in this code path. I have reviewed the newer source, but have not built or runtime-tested 2.4.5 or main; the Dovecot reproduction and workaround validation were done with 2.4.4.
Independent reproduction: The attached Python script reproduces the behavior without Dovecot, authentication, TLS, or mailbox access. It uses two separate processes, a UNIX control socket, a socketpair, and SCM_RIGHTS. A synchronization barrier controls when the receiver calls recvmsg().
On this Mac:
- Closing the sender's copy of the passed socketpair endpoint before the receiver calls recvmsg() makes the received endpoint return b'' (EOF); the other endpoint then encounters BrokenPipeError.
- Keeping that descriptor open until the receiver acknowledges receipt allows a complete PING/PONG exchange.
Run it with: python3 reproduce-macos-scm-rights.py
Local workaround: The attached patch against 2.4.4 stores the temporary descriptor in the client, closes it in login_callback() after the backend response, and also closes it during client cleanup for aborted sessions. With this change, IMAP LIST, SELECT, QRESYNC, and full message FETCH succeed over both STARTTLS and IMAPS. An authenticated SMTP -> LMTP -> storage -> IMAP TLS test also succeeds, and existing mailbox access continues to work.
I am reporting the observed macOS behavior and a working local workaround, rather than claiming to have established the underlying kernel cause or an upstream-ready fix.
Is this already a known issue? Would retaining the descriptor until the backend response be an appropriate portability fix? If not, which approach would you recommend? I would also appreciate guidance on a suitable regression test and whether you would like a pull request adapted to main.
Attachments:
- dovecot-macos-tls-handoff.patch
- reproduce-macos-scm-rights.py
- dovecot-n-anonymized.txt (current patched 2.4.4 configuration; credentials and identifying details redacted)
Thank you.
Hello Dovecot developers,
I would like to report a reproducible IMAP disconnection after successful authentication when Dovecot runs on macOS. Our investigation points to the lifetime of a socketpair descriptor passed from the login process to the post-login process using SCM_RIGHTS.
Environment:
- macOS 15.7, build 24G222; Darwin 24.6.0; arm64.
- Failure observed with Dovecot 2.4.3 and unpatched 2.4.4.
- Currently running Dovecot 2.4.4 with the attached local workaround.
- OpenSSL 3.6.2 in the 2.4.4 build.
- The 2.4.4 build uses the poll I/O loop and kqueue filesystem notifications.
- SQL/MySQL passdb and userdb, virtual users, sdbox storage.
- Mail storage is on a locally attached APFS volume, not NFS.
Observed behavior:
- passdb/userdb resolves the expected user and home directory.
- Plain IMAP on localhost can list folders and read messages.
- With STARTTLS or implicit TLS, authentication succeeds, but the connection closes before subsequent IMAP commands can complete.
- The failure was investigated using Apple Mail and Python IMAP probes against both STARTTLS on port 143 and IMAPS on port 993.
Source review: On 2026-10-05 I inspected release 2.4.5 and main at commit d59e22f26bb975173755026baa48af88db1b44ef. In src/login-common/sasl-proxy.c, master_send_request() still calls login_client_request() and immediately closes the temporary descriptor when close_fd is true. The corresponding file was named sasl-server.c in 2.4.4.
src/lib-login/login-client.c passes the descriptor through fd_send() without retaining a duplicate until the backend replies. client_get_plaintext_fd() in client-common.c creates the socketpair used for TLS proxying. I did not find an equivalent descriptor-lifetime workaround in this code path. I have reviewed the newer source, but have not built or runtime-tested 2.4.5 or main; the Dovecot reproduction and workaround validation were done with 2.4.4.
Independent reproduction: The attached Python script reproduces the behavior without Dovecot, authentication, TLS, or mailbox access. It uses two separate processes, a UNIX control socket, a socketpair, and SCM_RIGHTS. A synchronization barrier controls when the receiver calls recvmsg().
On this Mac:
- Closing the sender's copy of the passed socketpair endpoint before the receiver calls recvmsg() makes the received endpoint return b'' (EOF); the other endpoint then encounters BrokenPipeError.
- Keeping that descriptor open until the receiver acknowledges receipt allows a complete PING/PONG exchange.
Run it with: python3 reproduce-macos-scm-rights.py
Local workaround: The attached patch against 2.4.4 stores the temporary descriptor in the client, closes it in login_callback() after the backend response, and also closes it during client cleanup for aborted sessions. With this change, IMAP LIST, SELECT, QRESYNC, and full message FETCH succeed over both STARTTLS and IMAPS. An authenticated SMTP -> LMTP -> storage -> IMAP TLS test also succeeds, and existing mailbox access continues to work.
I am reporting the observed macOS behavior and a working local workaround, rather than claiming to have established the underlying kernel cause or an upstream-ready fix.
Is this already a known issue? Would retaining the descriptor until the backend response be an appropriate portability fix? If not, which approach would you recommend? I would also appreciate guidance on a suitable regression test and whether you would like a pull request adapted to main.
Attachments:
- dovecot-macos-tls-handoff.patch
- reproduce-macos-scm-rights.py
- dovecot-n-anonymized.txt (current patched 2.4.4 configuration; credentials and identifying details redacted)
Thank you.
participants (1)
-
Ignacio Pozo