From 244049bfd9c4c751844d7473050d008b45552667 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Mon, 31 Aug 2026 07:27:12 +0000 Subject: [PATCH 1/2] anvil: Use the handshake pid for the connection hash key The (service, pid) hash that maps a service process to its anvil connection used the pid from the UNIX socket credentials. On OSes where net_getunixcred() cannot return the peer's pid (macOS, FreeBSD before 13) that pid is -1, so every process of the same service hashed to the same key. Each new login process then logged "Handshake with duplicate service=... pid=-1 - replacing the old connection" and evicted its predecessor, leaving admin commands to fall back to connecting to the process's srv./ socket. Use the pid from the handshake instead. It is still verified against the credentials pid whenever the OS provides one, so on Linux and the other OSes with full credentials the key is unchanged. --- src/anvil/anvil-connection.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/anvil/anvil-connection.c b/src/anvil/anvil-connection.c index 0ea4feddb24..9a2d08363da 100644 --- a/src/anvil/anvil-connection.c +++ b/src/anvil/anvil-connection.c @@ -52,6 +52,8 @@ struct anvil_connection { enum anvil_connection_type conn_type; char *service; + /* pid of the connecting process, from the handshake */ + pid_t pid; bool fifo:1; bool added_to_hash:1; }; @@ -724,9 +726,14 @@ anvil_connection_handshake(struct anvil_connection *conn, conn->cmd_output = o_stream_multiplex_add_channel(conn->conn.output, ANVIL_CMD_CHANNEL_ID); + /* Use the handshake pid, which is verified above against the UNIX + credentials pid whenever the OS provides it. Some OSes (e.g. macOS) + can't return the pid at all, in which case remote_pid is -1 and all + the service's processes would collide in the hash. */ + conn->pid = pid; struct anvil_connection_key *hash_key, key = { .service = conn->service, - .pid = conn->conn.remote_pid, + .pid = conn->pid, }; struct anvil_connection *hash_conn; if (hash_table_lookup_full(anvil_connections_hash, &key, @@ -829,7 +836,7 @@ static void anvil_connection_destroy(struct connection *_conn) if (conn->added_to_hash) { struct anvil_connection_key *hash_key, key = { .service = conn->service, - .pid = conn->conn.remote_pid, + .pid = conn->pid, }; struct anvil_connection *hash_conn; if (!hash_table_lookup_full(anvil_connections_hash, &key, -- GitLab From e165e490b435911277a5714cf2cae5216306f581 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Mon, 31 Aug 2026 07:27:12 +0000 Subject: [PATCH 2/2] lib: net_getunixcred() - Return the peer's pid on FreeBSD 13+ FreeBSD 13 added cr_pid to struct xucred, but getsockopt(LOCAL_PEERCRED) was only tried after getpeereid(), which cannot return a pid. So the LOCAL_PEERCRED code was unreachable on FreeBSD and the pid was always returned as -1. Check for the new struct member in configure and, when it exists, use LOCAL_PEERCRED in preference to getpeereid(). Where xucred has no pid (macOS, older FreeBSD) getpeereid() is still preferred, as before. Based on patch by Lucas Holt --- configure.ac | 9 +++++++++ src/lib/net.c | 27 +++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index 8dcc55e0c66..3ce442fee21 100644 --- a/configure.ac +++ b/configure.ac @@ -359,6 +359,15 @@ AC_CHECK_HEADERS([valgrind/valgrind.h]) DOVECOT_SOCKPEERCRED +dnl FreeBSD 13+ added the peer's pid to struct xucred. +AC_CHECK_MEMBERS([struct xucred.cr_pid],,,[ +#include +#include +#ifdef HAVE_SYS_UCRED_H +# include +#endif +]) + DOVECOT_TYPEOF DOVECOT_IOLOOP DOVECOT_NOTIFY diff --git a/src/lib/net.c b/src/lib/net.c index 5bfd92ffec8..510299a0b3e 100644 --- a/src/lib/net.c +++ b/src/lib/net.c @@ -832,16 +832,11 @@ int net_getunixcred(int fd, struct net_unix_cred *cred_r) cred_r->gid = ucred.unp_egid; cred_r->pid = ucred.unp_pid; return 0; -#elif defined(HAVE_GETPEEREID) - /* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */ - if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) { - i_error("getpeereid() failed: %m"); - return -1; - } - cred_r->pid = (pid_t)-1; - return 0; -#elif defined(LOCAL_PEERCRED) - /* Older FreeBSD */ +#elif defined(LOCAL_PEERCRED) && \ + (defined(HAVE_STRUCT_XUCRED_CR_PID) || !defined(HAVE_GETPEEREID)) + /* FreeBSD, macOS. getpeereid() usually exists as well, but it can't + return the pid. Prefer it only if struct xucred has no pid either + (FreeBSD <13, macOS). */ struct xucred ucred; socklen_t len = sizeof(ucred); @@ -857,6 +852,18 @@ int net_getunixcred(int fd, struct net_unix_cred *cred_r) cred_r->uid = ucred.cr_uid; cred_r->gid = ucred.cr_gid; +# ifdef HAVE_STRUCT_XUCRED_CR_PID + cred_r->pid = ucred.cr_pid; +# else + cred_r->pid = (pid_t)-1; +# endif + return 0; +#elif defined(HAVE_GETPEEREID) + /* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */ + if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) { + i_error("getpeereid() failed: %m"); + return -1; + } cred_r->pid = (pid_t)-1; return 0; #elif defined(HAVE_GETPEERUCRED) -- GitLab