From: Tobias Brunner Date: Tue, 2 Dec 2025 15:15:42 +0000 (+0100) Subject: agent: Add option to open socket as specific user X-Git-Tag: 6.0.4~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8b467ad39a7675be08339ed8a36029afa560e572;p=thirdparty%2Fstrongswan.git agent: Add option to open socket as specific user This can prevent an attack where user A passes the path to user B's ssh-agent socket to the daemon that is running as root. --- diff --git a/src/libstrongswan/credentials/builder.c b/src/libstrongswan/credentials/builder.c index 27ae83c8fa..76bcd3917e 100644 --- a/src/libstrongswan/credentials/builder.c +++ b/src/libstrongswan/credentials/builder.c @@ -20,6 +20,7 @@ ENUM(builder_part_names, BUILD_FROM_FILE, BUILD_END, "BUILD_FROM_FILE", "BUILD_AGENT_SOCKET", + "BUILD_AGENT_USER", "BUILD_BLOB", "BUILD_BLOB_ASN1_DER", "BUILD_BLOB_PEM", diff --git a/src/libstrongswan/credentials/builder.h b/src/libstrongswan/credentials/builder.h index e95265e4c6..7e2a239a33 100644 --- a/src/libstrongswan/credentials/builder.h +++ b/src/libstrongswan/credentials/builder.h @@ -49,6 +49,8 @@ enum builder_part_t { BUILD_FROM_FILE, /** unix socket of a ssh/pgp agent, char* */ BUILD_AGENT_SOCKET, + /** user to access a ssh/pgp agent socket, char* */ + BUILD_AGENT_USER, /** An arbitrary blob of data, chunk_t */ BUILD_BLOB, /** DER encoded ASN.1 blob, chunk_t */ diff --git a/src/libstrongswan/plugins/agent/agent_plugin.c b/src/libstrongswan/plugins/agent/agent_plugin.c index 574100a733..958e3b2148 100644 --- a/src/libstrongswan/plugins/agent/agent_plugin.c +++ b/src/libstrongswan/plugins/agent/agent_plugin.c @@ -70,6 +70,13 @@ PLUGIN_DEFINE(agent) DBG1(DBG_DMN, "agent plugin requires CAP_DAC_OVERRIDE capability"); return NULL; } + /* required to switch user/group to access ssh-agent socket */ + if (!lib->caps->keep(lib->caps, CAP_SETUID) || + !lib->caps->keep(lib->caps, CAP_SETGID)) + { + DBG1(DBG_DMN, "agent plugin requires CAP_SETUID/CAP_SETGID capability"); + return NULL; + } INIT(this, .public = { diff --git a/src/libstrongswan/plugins/agent/agent_private_key.c b/src/libstrongswan/plugins/agent/agent_private_key.c index 0c1c9887ab..c1d371cb6f 100644 --- a/src/libstrongswan/plugins/agent/agent_private_key.c +++ b/src/libstrongswan/plugins/agent/agent_private_key.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2019 Tobias Brunner + * Copyright (C) 2013-2025 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -22,7 +22,10 @@ #include #include #include +#include #include +#include +#include #include #include @@ -50,6 +53,11 @@ struct private_agent_private_key_t { */ char *path; + /** + * Optional user to connect to socket as + */ + char *user; + /** * public key encoded in SSH format */ @@ -142,12 +150,24 @@ static chunk_t read_string(chunk_t *blob) } /** - * open socket connection to the ssh-agent + * Connect a UNIX socket to the given path. */ -static int open_connection(char *path) +static bool connect_socket(int fd, char *path) { struct sockaddr_un addr; - int s; + + addr.sun_family = AF_UNIX; + addr.sun_path[UNIX_PATH_MAX - 1] = '\0'; + strncpy(addr.sun_path, path, UNIX_PATH_MAX - 1); + return connect(fd, (struct sockaddr*)&addr, SUN_LEN(&addr)) == 0; +} + +/** + * Open socket connection to the ssh-agent, optionally as a given user. + */ +static int open_connection(char *path, char *user) +{ + int s, pid, status; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) @@ -157,14 +177,61 @@ static int open_connection(char *path) return -1; } - addr.sun_family = AF_UNIX; - addr.sun_path[UNIX_PATH_MAX - 1] = '\0'; - strncpy(addr.sun_path, path, UNIX_PATH_MAX - 1); - - if (connect(s, (struct sockaddr*)&addr, SUN_LEN(&addr)) != 0) + if (user) + { + pid = fork(); + switch (pid) + { + case -1: + DBG1(DBG_LIB, "forking failed after opening ssh-agent " + "socket: %s", strerror(errno)); + close(s); + return -1; + case 0: + /* child, do everything manually to avoid interacting with + * mutexes etc. that are potentially locked in the parent */ + struct passwd *pwp; + + pwp = getpwnam(user); + if (pwp) + { + if (initgroups(user, pwp->pw_gid) == 0) + { + if (setgid(pwp->pw_gid) == 0 && + setuid(pwp->pw_uid) == 0) + { + if (connect_socket(s, path)) + { + exit(EXIT_SUCCESS); + } + } + } + } + exit(EXIT_FAILURE); + /* not reached */ + default: + /* parent */ + if (waitpid(pid, &status, 0) == -1 || + !WIFEXITED(status)) + { + DBG1(DBG_LIB, "sub-process to connect to ssh-agent didn't " + "terminate normally"); + close(s); + return -1; + } + if (WEXITSTATUS(status) != 0) + { + DBG1(DBG_LIB, "connecting to ssh-agent in sub-process " + "failed: %d", WEXITSTATUS(status)); + close(s); + return -1; + } + } + } + else if (!connect_socket(s, path)) { DBG1(DBG_LIB, "connecting to ssh-agent socket '%s' failed: %s", - addr.sun_path, strerror(errno)); + path, strerror(errno)); close(s); return -1; } @@ -181,7 +248,7 @@ static bool read_key(private_agent_private_key_t *this, public_key_t *pubkey) chunk_t blob, key; bool success = FALSE; - socket = open_connection(this->path); + socket = open_connection(this->path, this->user); if (socket < 0) { return FALSE; @@ -293,7 +360,7 @@ METHOD(private_key_t, sign, bool, return FALSE; } - socket = open_connection(this->path); + socket = open_connection(this->path, this->user); if (socket < 0) { return FALSE; @@ -512,6 +579,7 @@ METHOD(private_key_t, destroy, void, chunk_free(&this->key); DESTROY_IF(this->pubkey); free(this->path); + free(this->user); free(this); } } @@ -523,7 +591,7 @@ agent_private_key_t *agent_private_key_open(key_type_t type, va_list args) { private_agent_private_key_t *this; public_key_t *pubkey = NULL; - char *path = NULL; + char *path = NULL, *user = NULL; while (TRUE) { @@ -532,6 +600,9 @@ agent_private_key_t *agent_private_key_open(key_type_t type, va_list args) case BUILD_AGENT_SOCKET: path = va_arg(args, char*); continue; + case BUILD_AGENT_USER: + user = va_arg(args, char*); + continue; case BUILD_PUBLIC_KEY: pubkey = va_arg(args, public_key_t*); continue; @@ -566,6 +637,7 @@ agent_private_key_t *agent_private_key_open(key_type_t type, va_list args) }, }, .path = strdup(path), + .user = strdupnull(user), .ref = 1, ); diff --git a/src/libstrongswan/utils/capabilities.h b/src/libstrongswan/utils/capabilities.h index 681fbb8bfd..9ecde471f6 100644 --- a/src/libstrongswan/utils/capabilities.h +++ b/src/libstrongswan/utils/capabilities.h @@ -50,6 +50,12 @@ typedef struct capabilities_t capabilities_t; #ifndef CAP_SETPCAP # define CAP_SETPCAP 8 #endif +#ifndef CAP_SETUID +# define CAP_SETUID 7 +#endif +#ifndef CAP_SETGID +# define CAP_SETGID 6 +#endif /** * POSIX capability dropping abstraction layer.