Commit 8b467ad39a for strongswan.org

commit 8b467ad39a7675be08339ed8a36029afa560e572
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Tue Dec 2 16:15:42 2025 +0100

    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 <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,
 	);

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.