From 80a160843bc64ef3fd0e43bf227eaa6ef95923de Mon Sep 17 00:00:00 2001
From: Daniel Markstedt <daniel@mindani.net>
Date: Wed, 6 May 2026 21:25:05 +0200
Subject: [PATCH] CVE-2026-44054: afpd: randomize reconnect session token

Reported-by: @00redbeer
Signed-off-by: Daniel Markstedt <daniel@mindani.net>
---
 etc/afpd/auth.c              | 39 +++++-----------
 include/atalk/server_child.h |  8 +++-
 include/atalk/server_ipc.h   |  1 +
 libatalk/util/server_child.c | 86 +++++++++++++++++++++++++++---------
 libatalk/util/server_ipc.c   | 31 +++++++++----
 5 files changed, 106 insertions(+), 59 deletions(-)

diff --git a/etc/afpd/auth.c b/etc/afpd/auth.c
index 0793363c..4b97982d 100644
--- a/etc/afpd/auth.c
+++ b/etc/afpd/auth.c
@@ -412,22 +412,20 @@ int afp_zzz(AFPObj *obj, char *ibuf, size_t ibuflen, char *rbuf,
 /* ---------------------- */
 static int create_session_token(AFPObj *obj)
 {
-    pid_t pid;
-
-    /* use 8 bytes for token as OSX, don't know if it helps */
-    if (sizeof(pid_t) > SESSIONTOKEN_LEN) {
-        LOG(log_error, logtype_afpd, "sizeof(pid_t) > %u", SESSIONTOKEN_LEN);
+    if (NULL == (obj->sinfo.sessiontoken = malloc(SESSIONTOKEN_LEN))) {
         return AFPERR_MISC;
     }
 
-    if (NULL == (obj->sinfo.sessiontoken = malloc(SESSIONTOKEN_LEN))) {
+    obj->sinfo.sessiontoken_len = SESSIONTOKEN_LEN;
+
+    if (uam_random_string(obj, obj->sinfo.sessiontoken, SESSIONTOKEN_LEN) < 0) {
+        free(obj->sinfo.sessiontoken);
+        obj->sinfo.sessiontoken = NULL;
         return AFPERR_MISC;
     }
 
-    memset(obj->sinfo.sessiontoken, 0, SESSIONTOKEN_LEN);
-    obj->sinfo.sessiontoken_len = SESSIONTOKEN_LEN;
-    pid = getpid();
-    memcpy(obj->sinfo.sessiontoken, &pid, sizeof(pid_t));
+    ipc_child_write(obj->ipc_fd, IPC_SESSIONTOKEN, SESSIONTOKEN_LEN,
+                    obj->sinfo.sessiontoken);
     return 0;
 }
 
@@ -575,8 +573,6 @@ int afp_disconnect(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_,
     DSI                 *dsi = (DSI *)obj->dsi;
     uint16_t           type;
     uint32_t           tklen;
-    pid_t               token;
-    int                 i;
     *rbuflen = 0;
     ibuf += 2;
 #if 0
@@ -594,26 +590,10 @@ int afp_disconnect(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_,
     tklen = ntohl(tklen);
     ibuf += sizeof(tklen);
 
-    if (sizeof(pid_t) > SESSIONTOKEN_LEN) {
-        LOG(log_error, logtype_afpd, "sizeof(pid_t) > %u", SESSIONTOKEN_LEN);
-        return AFPERR_MISC;
-    }
-
     if (tklen != SESSIONTOKEN_LEN) {
         return AFPERR_MISC;
     }
 
-    tklen = sizeof(pid_t);
-    memcpy(&token, ibuf, tklen);
-    /* our stuff is pid + zero pad */
-    ibuf += tklen;
-
-    for (i = tklen; i < SESSIONTOKEN_LEN; i++, ibuf++) {
-        if (*ibuf != 0) {
-            return AFPERR_MISC;
-        }
-    }
-
     LOG(log_note, logtype_afpd, "afp_disconnect: trying primary reconnect");
     dsi->flags |= DSI_RECONINPROG;
     /* Deactivate tickle timer */
@@ -621,7 +601,8 @@ int afp_disconnect(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_,
     setitimer(ITIMER_REAL, &none, NULL);
 
     /* check for old session, possibly transferring session from here to there */
-    if (ipc_child_write(obj->ipc_fd, IPC_DISCOLDSESSION, tklen, &token) != 0) {
+    if (ipc_child_write(obj->ipc_fd, IPC_DISCOLDSESSION, SESSIONTOKEN_LEN,
+                        ibuf) != 0) {
         goto exit;
     }
 
diff --git a/include/atalk/server_child.h b/include/atalk/server_child.h
index 8796f18a..28364693 100644
--- a/include/atalk/server_child.h
+++ b/include/atalk/server_child.h
@@ -31,6 +31,8 @@ typedef struct afp_child {
     int       afpch_ipc_fd;    /*!< socket for IPC bw afpd parent and childs */
     int16_t   afpch_state;     /*!< state of AFP session (eg active, sleeping, disconnected) */
     char     *afpch_volumes;   /*!< mounted volumes */
+    char     *afpch_sessiontoken;     /*!< session token for reconnect */
+    uint32_t  afpch_sessiontoken_len;
     struct afp_child **afpch_prevp;
     struct afp_child *afpch_next;
 } afp_child_t;
@@ -54,8 +56,10 @@ extern void server_child_kill(server_child_t *, int);
 extern void server_child_kill_one_by_id(server_child_t *children, pid_t pid,
                                         uid_t, uint32_t len, char *id,
                                         uint32_t boottime);
-extern int  server_child_transfer_session(server_child_t *children, pid_t,
-        uid_t, int, uint16_t);
+extern int  server_child_transfer_session(server_child_t *children,
+        const char *token, uint32_t tokenlen, uid_t, int, uint16_t);
+extern void server_child_set_token(server_child_t *children, pid_t pid,
+                                   const char *token, uint32_t tokenlen);
 extern void server_child_handler(server_child_t *);
 extern void server_child_login_done(server_child_t *children, pid_t pid,
                                     uid_t);
diff --git a/include/atalk/server_ipc.h b/include/atalk/server_ipc.h
index 8c11f619..a6e31bde 100644
--- a/include/atalk/server_ipc.h
+++ b/include/atalk/server_ipc.h
@@ -10,6 +10,7 @@
 #define IPC_STATE            2  /*!< pass AFP session state */
 #define IPC_VOLUMES          3  /*!< pass list of open volumes */
 #define IPC_LOGINDONE        4
+#define IPC_SESSIONTOKEN     5  /*!< pass session token to parent for reconnect lookup */
 
 extern int ipc_server_read(server_child_t *children, int fd);
 extern int ipc_child_write(int fd, uint16_t command, int len, void *token);
diff --git a/libatalk/util/server_child.c b/libatalk/util/server_child.c
index b27e66a3..43fc300d 100644
--- a/libatalk/util/server_child.c
+++ b/libatalk/util/server_child.c
@@ -156,6 +156,11 @@ int server_child_remove(server_child_t *children, pid_t pid)
         child->afpch_clientid = NULL;
     }
 
+    if (child->afpch_sessiontoken) {
+        free(child->afpch_sessiontoken);
+        child->afpch_sessiontoken = NULL;
+    }
+
     /* In main:child_handler() we need the fd in order to remove it from the pollfd set */
     fd = child->afpch_ipc_fd;
 
@@ -195,6 +200,10 @@ void server_child_free(server_child_t *children)
                 free(child->afpch_volumes);
             }
 
+            if (child->afpch_sessiontoken) {
+                free(child->afpch_sessiontoken);
+            }
+
             free(child);
             child = tmp;
         }
@@ -238,62 +247,99 @@ static int kill_child(afp_child_t *child)
 }
 
 /*!
- * @brief Try to find an old session and pass socket
+ * @brief Try to find an old session by token and pass socket
  * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed
  */
 int server_child_transfer_session(server_child_t *children,
-                                  pid_t pid,
+                                  const char *token,
+                                  uint32_t tokenlen,
                                   uid_t uid,
                                   int afp_socket,
                                   uint16_t DSI_requestID)
 {
     EC_INIT;
-    afp_child_t *child;
-
-    if ((child = server_child_resolve(children, pid)) == NULL) {
-        LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid);
+    afp_child_t *child = NULL;
+    int i;
+    pthread_mutex_lock(&children->servch_lock);
 
-        if (kill(pid, 0) == 0) {
-            LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid);
-            kill(pid, SIGTERM);
-            sleep(2);
+    for (i = 0; i < CHILD_HASHSIZE && child == NULL; i++) {
+        afp_child_t *c;
 
-            if (kill(pid, 0) == 0) {
-                LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid);
-                kill(pid, SIGKILL);
-                sleep(2);
+        for (c = children->servch_table[i]; c; c = c->afpch_next) {
+            if (c->afpch_sessiontoken_len == tokenlen
+                    && c->afpch_sessiontoken != NULL
+                    && memcmp(c->afpch_sessiontoken, token, tokenlen) == 0) {
+                child = c;
+                break;
             }
         }
+    }
+
+    pthread_mutex_unlock(&children->servch_lock);
 
+    if (child == NULL) {
+        LOG(log_note, logtype_default,
+            "Reconnect: no child with matching session token");
         return 0;
     }
 
     if (!child->afpch_valid) {
-        /* hmm, client 'guess' the pid, rogue? */
-        LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid);
+        LOG(log_error, logtype_default, "Reconnect: child[%u] not yet valid",
+            child->afpch_pid);
         return 0;
     } else if (child->afpch_uid != uid) {
-        LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid);
+        LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user",
+            child->afpch_pid);
         return 0;
     }
 
     LOG(log_note, logtype_default, "Reconnect: transferring session to child[%u]",
-        pid);
+        child->afpch_pid);
 
     if (writet(child->afpch_ipc_fd, &DSI_requestID, 2, 0, 2) != 2) {
         LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]",
-            pid);
+            child->afpch_pid);
         EC_STATUS(-1);
         goto EC_CLEANUP;
     }
 
     EC_ZERO_LOG(send_fd(child->afpch_ipc_fd, afp_socket));
-    EC_ZERO_LOG(kill(pid, SIGURG));
+    EC_ZERO_LOG(kill(child->afpch_pid, SIGURG));
     EC_STATUS(1);
 EC_CLEANUP:
     EC_EXIT;
 }
 
+void server_child_set_token(server_child_t *children, pid_t pid,
+                            const char *token, uint32_t tokenlen)
+{
+    afp_child_t *child;
+    char *buf;
+
+    if ((buf = malloc(tokenlen)) == NULL) {
+        return;
+    }
+
+    memcpy(buf, token, tokenlen);
+    pthread_mutex_lock(&children->servch_lock);
+
+    if ((child = server_child_resolve(children, pid)) != NULL) {
+        if (child->afpch_sessiontoken) {
+            free(child->afpch_sessiontoken);
+        }
+
+        child->afpch_sessiontoken = buf;
+        child->afpch_sessiontoken_len = tokenlen;
+        buf = NULL;
+    }
+
+    pthread_mutex_unlock(&children->servch_lock);
+
+    if (buf) {
+        free(buf);
+    }
+}
+
 
 /*!
  * @brief see if there is a process for the same mac
diff --git a/libatalk/util/server_ipc.c b/libatalk/util/server_ipc.c
index 5c4a9dc3..25888f4a 100644
--- a/libatalk/util/server_ipc.c
+++ b/libatalk/util/server_ipc.c
@@ -48,30 +48,38 @@ static char *ipc_cmd_str[] = { "IPC_DISCOLDSESSION",
                                "IPC_GETSESSION",
                                "IPC_STATE",
                                "IPC_VOLUMES",
-                               "IPC_LOGINDONE"
+                               "IPC_LOGINDONE",
+                               "IPC_SESSIONTOKEN"
                              };
 
 /*!
- * @brief Pass afp_socket to old disconnected session if one has a matching token (token = pid)
+ * @brief Pass afp_socket to old disconnected session matching the random session token
  * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed
  */
 static int ipc_kill_token(struct ipc_header *ipc, server_child_t *children)
 {
-    pid_t pid;
-
-    if (ipc->len != sizeof(pid_t)) {
+    if (ipc->len == 0 || ipc->msg == NULL) {
         return -1;
     }
 
-    /* assume signals SA_RESTART set */
-    memcpy(&pid, ipc->msg, sizeof(pid_t));
     return server_child_transfer_session(children,
-                                         pid,
+                                         ipc->msg,
+                                         ipc->len,
                                          ipc->uid,
                                          ipc->afp_socket,
                                          ipc->DSI_requestID);
 }
 
+static int ipc_session_token(struct ipc_header *ipc, server_child_t *children)
+{
+    if (ipc->len == 0 || ipc->msg == NULL) {
+        return -1;
+    }
+
+    server_child_set_token(children, ipc->child_pid, ipc->msg, ipc->len);
+    return 0;
+}
+
 /* ----------------- */
 static int ipc_get_session(struct ipc_header *ipc, server_child_t *children)
 {
@@ -288,6 +296,13 @@ int ipc_server_read(server_child_t *children, int fd)
 
         break;
 
+    case IPC_SESSIONTOKEN:
+        if (ipc_session_token(&ipc, children) != 0) {
+            return -1;
+        }
+
+        break;
+
     default:
         LOG(log_info, logtype_afpd, "ipc_read: unknown command: %d", ipc.command);
         return -1;
