This is an automated email from the git hooks/post-receive script.

git pushed a commit to reference refs/pull/36/head
in repository terminology.

View the commit online.

commit f585ff95f0d4d41bbe8a0f13d0d49fc20c6a60db
Author: [email protected] <[email protected]>
AuthorDate: Mon May 4 20:43:21 2026 -0600

    fix(tymux): resolve double-read race in ATTACH handshake
    
    A latent double-read bug has blocked `tymux new <name>` since commit
    afa7bea9. Both tymuxd.c:_cb_client_connect and termd_tymux.c:termd_tymux_client_add
    attempted to recv() the ATTACH message from the same fd.
    
    _cb_client_connect did recv(cfd, &hdr, sizeof(hdr), MSG_WAITALL) and
    drained the payload, then handed cfd to termd_tymux_client_add.
    termd_tymux_client_add then called termsrv_msg_recv — but the bytes had
    already been consumed. termsrv_msg_recv uses MSG_DONTWAIT|MSG_PEEK, which
    returns EAGAIN immediately on a freshly-accepted fd (MSG_DONTWAIT overrides
    SO_RCVTIMEO), the retry-once path also got EAGAIN, and the function bailed
    with "expected ATTACH from fd N, got 0". The daemon closed the socket
    without sending ATTACHED, and the proxy errored "did not receive valid
    ATTACHED message".
    
    Fix: tymuxd.c removes the ATTACH recv and payload drain; it now only
    accept()s and hands cfd to termd_tymux_client_add. termd_tymux.c replaces
    the termsrv_msg_recv call with a direct blocking recv(..., MSG_WAITALL)
    for header and payload, preserving the 1-second SO_RCVTIMEO and clearing
    it after the read. ATTACH is now read exactly once by its sole owner.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/bin/termd_tymux.c | 64 +++++++++++++++++++++++++++++++--------------------
 src/bin/tymuxd.c      | 34 ++++-----------------------
 2 files changed, 44 insertions(+), 54 deletions(-)

diff --git a/src/bin/termd_tymux.c b/src/bin/termd_tymux.c
index b2ca1b81..f0c06ea6 100644
--- a/src/bin/termd_tymux.c
+++ b/src/bin/termd_tymux.c
@@ -774,7 +774,6 @@ termd_tymux_client_add(TermDTymux *sess, int cfd)
    TermDClient      *client;
    void             *payload = NULL;
    uint32_t          size    = 0;
-   int               type;
    TermSrvAttachMode mode    = TSRV_ATTACH_HIJACK;
 
    EINA_SAFETY_ON_NULL_RETURN(sess);
@@ -782,38 +781,53 @@ termd_tymux_client_add(TermDTymux *sess, int cfd)
      return;
 
    /* Read the ATTACH message the client sends immediately on connect.
-    * Set a 1-second receive timeout so a client that connects but
-    * never sends cannot block the event loop indefinitely. */
+    * termsrv_msg_recv uses MSG_DONTWAIT|MSG_PEEK and so cannot block for
+    * bytes-in-flight; a freshly-accepted fd may not have ATTACH delivered
+    * yet, which would race to EAGAIN.  Use a blocking MSG_WAITALL recv
+    * with a 1-second SO_RCVTIMEO instead, then clear the timeout for
+    * normal non-blocking event-loop I/O. */
    {
       struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
       setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    }
-   type = termsrv_msg_recv(cfd, &payload, &size, NULL);
-   if (type == 0)
-     {
-        /* EAGAIN — retry once */
-        free(payload); payload = NULL; size = 0;
-        type = termsrv_msg_recv(cfd, &payload, &size, NULL);
-     }
-   /* Clear the timeout so normal event-loop I/O is non-blocking again */
+   {
+      TermSrvMsgHdr hdr;
+      ssize_t       n = recv(cfd, &hdr, sizeof(hdr), MSG_WAITALL);
+      if (n != (ssize_t)sizeof(hdr) || hdr.type != TSRV_MSG_ATTACH)
+        {
+           ERR("expected ATTACH from fd %d (n=%zd type=%u)",
+               cfd, n, (n == (ssize_t)sizeof(hdr)) ? hdr.type : 0u);
+           close(cfd);
+           return;
+        }
+      if (hdr.size > TERMSRV_MAX_MSG_PAYLOAD)
+        {
+           ERR("ATTACH payload too large (%u) from fd %d", hdr.size, cfd);
+           close(cfd);
+           return;
+        }
+      if (hdr.size > 0)
+        {
+           payload = malloc(hdr.size);
+           if (!payload)
+             {
+                close(cfd);
+                return;
+             }
+           if (recv(cfd, payload, hdr.size, MSG_WAITALL) != (ssize_t)hdr.size)
+             {
+                ERR("short ATTACH payload read from fd %d", cfd);
+                free(payload);
+                close(cfd);
+                return;
+             }
+           size = hdr.size;
+        }
+   }
    {
       struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };
       setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    }
-   if (type < 0)
-     {
-        ERR("failed to read ATTACH message from fd %d", cfd);
-        free(payload);
-        close(cfd);
-        return;
-     }
-   if (type != TSRV_MSG_ATTACH)
-     {
-        ERR("expected ATTACH (%d) from fd %d, got %d", TSRV_MSG_ATTACH, cfd, type);
-        free(payload);
-        close(cfd);
-        return;
-     }
 
    /* Extract mode from first payload byte; default HIJACK if absent */
    if (payload && size >= 1)
diff --git a/src/bin/tymuxd.c b/src/bin/tymuxd.c
index e6898ee1..aa37319a 100644
--- a/src/bin/tymuxd.c
+++ b/src/bin/tymuxd.c
@@ -32,9 +32,7 @@ static Eina_Bool
 _cb_client_connect(void *data EINA_UNUSED,
                    Ecore_Fd_Handler *fd_handler EINA_UNUSED)
 {
-   int            cfd;
-   TermSrvMsgHdr  hdr;
-   ssize_t        n;
+   int cfd;
 
    cfd = accept(_listen_fd, NULL, NULL);
    if (cfd < 0)
@@ -44,32 +42,10 @@ _cb_client_connect(void *data EINA_UNUSED,
         return ECORE_CALLBACK_RENEW;
      }
 
-   /* Read the ATTACH header with MSG_WAITALL so we do not race against the
-    * kernel delivering the bytes.  The newly-accepted fd may not have data
-    * ready yet when accept() returns, so MSG_DONTWAIT (used by the normal
-    * termsrv_msg_recv path) would return EAGAIN → type 0 → drop the client
-    * silently. */
-   n = recv(cfd, &hdr, sizeof(hdr), MSG_WAITALL);
-   if (n != (ssize_t)sizeof(hdr) || hdr.type != TSRV_MSG_ATTACH)
-     {
-        ERR("expected ATTACH from new client (hdr.type=%u n=%zd), closing fd %d",
-            (n == (ssize_t)sizeof(hdr)) ? hdr.type : 0u, n, cfd);
-        close(cfd);
-        return ECORE_CALLBACK_RENEW;
-     }
-
-   /* Drain any payload the client sent with the ATTACH (tymux name). */
-   if (hdr.size > 0 && hdr.size <= TERMSRV_MAX_MSG_PAYLOAD)
-     {
-        void *tmp = malloc(hdr.size);
-        if (tmp)
-          {
-             recv(cfd, tmp, hdr.size, MSG_WAITALL);
-             free(tmp);
-          }
-     }
-
-   termd_tymux_client_add( _tymux, cfd);
+   /* termd_tymux_client_add reads and validates ATTACH itself (with a
+    * 1-second SO_RCVTIMEO to handle the race where bytes are not yet
+    * delivered when accept() returns). */
+   termd_tymux_client_add(_tymux, cfd);
    return ECORE_CALLBACK_RENEW;
 }
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to