From 80a160843bc64ef3fd0e43bf227eaa6ef95923de Mon Sep 17 00:00:00 2001 From: Daniel Markstedt 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 --- 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;