]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
agent: Add option to open socket as specific user
authorTobias Brunner <tobias@strongswan.org>
Tue, 2 Dec 2025 15:15:42 +0000 (16:15 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 10 Dec 2025 17:34:19 +0000 (18:34 +0100)
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.

src/libstrongswan/credentials/builder.c
src/libstrongswan/credentials/builder.h
src/libstrongswan/plugins/agent/agent_plugin.c
src/libstrongswan/plugins/agent/agent_private_key.c
src/libstrongswan/utils/capabilities.h

index 27ae83c8fa33ef781e75670ae7e7236916c6f8c9..76bcd3917e7c21e6b453a9ca1da59e0f68180a9e 100644 (file)
@@ -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",
index e95265e4c60ed8908258974621decbe42a90310c..7e2a239a33f33fbea518c5d8589a33c9774d3554 100644 (file)
@@ -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 */
index 574100a733f9bd04a68af36ef134da66153475d2..958e3b214835f98aec1ff8c255bfe5a1edd06457 100644 (file)
@@ -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 = {
index 0c1c9887ab175dc651a5c7989789147ffb07d9eb..c1d371cb6f6d9acbe2cac10ffe7ba13f02b92582 100644 (file)
@@ -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
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <sys/wait.h>
 #include <arpa/inet.h>
+#include <pwd.h>
+#include <grp.h>
 #include <errno.h>
 
 #include <library.h>
@@ -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,
        );
 
index 681fbb8bfd0f0ccc9bf6cfaa2befa75d77bc4364..9ecde471f6a22aed7222c9eee6d2f3888657b20a 100644 (file)
@@ -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.