]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Move agent listener sockets from /tmp to under
authordjm@openbsd.org <djm@openbsd.org>
Mon, 5 May 2025 02:48:06 +0000 (02:48 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 5 May 2025 03:35:21 +0000 (13:35 +1000)
~/.ssh/agent for both ssh-agent(1) and forwarded sockets in sshd(8).

This ensures processes (such as Firefox) that have restricted
filesystem access that includes /tmp (via unveil(3)) do not have the
ability to use keys in an agent.

Moving the default directory has the consequence that the OS will no
longer clean up stale agent sockets, so ssh-agent now gains this
ability.

To support $HOME on NFS, the socket path includes a truncated hash of
the hostname. ssh-agent will by default only clean up sockets from
the same hostname.

ssh-agent gains some new flags: -U suppresses the automatic cleanup
of stale sockets when it starts. -u forces a cleanup without
keeping a running agent, -uu forces a cleanup that ignores the
hostname. -T makes ssh-agent put the socket back in /tmp.

feedback deraadt@ naddy@, doitdoitdoit deraadt@

OpenBSD-Commit-ID: 8383dabd98092fe5498d5f7f15c7d314b03a93e1

Makefile.in
hostfile.c
misc.c
misc.h
pathnames.h
session.c
ssh-agent.1
ssh-agent.c

index f15ac558aea7e692e23dc9729fc7dd9f5df88b3e..4550e17951f4a6fb274d2573884cb8236b28fa9e 100644 (file)
@@ -140,7 +140,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \
        auth2-gss.o gss-serv.o gss-serv-krb5.o \
        loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
        sftp-server.o sftp-common.o \
-       uidswap.o platform-listen.o $(SKOBJS)
+       uidswap.o platform-listen.o misc-agent.o $(SKOBJS)
 
 SSHD_AUTH_OBJS=sshd-auth.o \
        auth2-methods.o \
@@ -155,7 +155,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \
        sandbox-null.o sandbox-rlimit.o sandbox-darwin.o \
        sandbox-seccomp-filter.o sandbox-capsicum.o  sandbox-solaris.o \
        sftp-server.o sftp-common.o \
-       uidswap.o $(SKOBJS)
+       uidswap.o misc-agent.o $(SKOBJS)
 
 SFTP_CLIENT_OBJS=sftp-common.o sftp-client.o sftp-glob.o
 
@@ -163,7 +163,7 @@ SCP_OBJS=   scp.o progressmeter.o $(SFTP_CLIENT_OBJS)
 
 SSHADD_OBJS=   ssh-add.o $(SKOBJS)
 
-SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o $(SKOBJS)
+SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o misc-agent.o $(SKOBJS)
 
 SSHKEYGEN_OBJS=        ssh-keygen.o sshsig.o $(SKOBJS)
 
index e941fc4509ad0df892a88062966146707bcc4835..4b4a0e31ef384fc0f4a10bc17517779f018807bd 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: hostfile.c,v 1.97 2025/04/30 05:26:15 djm Exp $ */
+/* $OpenBSD: hostfile.c,v 1.98 2025/05/05 02:48:07 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
diff --git a/misc.c b/misc.c
index dd0bd032ae3c0984e00c45edd2e513d49a56709b..25465dcd2d4fd9c68482a0ccff7a4939229755b2 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.198 2024/10/24 03:14:37 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.199 2025/05/05 02:48:06 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005-2020 Damien Miller.  All rights reserved.
@@ -3138,3 +3138,18 @@ signal_is_crash(int sig)
        }
        return 0;
 }
+
+char *
+get_homedir(void)
+{
+       char *cp;
+       struct passwd *pw;
+
+       if ((cp = getenv("HOME")) != NULL && *cp != '\0')
+               return xstrdup(cp);
+
+       if ((pw = getpwuid(getuid())) != NULL && *pw->pw_dir != '\0')
+               return xstrdup(pw->pw_dir);
+
+       return NULL;
+}
diff --git a/misc.h b/misc.h
index efecdf1ad6f93cc89b7ace84ac7a7069c7a54493..a7afa23e8e9293f6f6acdb48da6d2939873ac2e6 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.110 2024/09/25 01:24:04 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.111 2025/05/05 02:48:06 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -108,6 +108,7 @@ int  parse_pattern_interval(const char *, char **, int *);
 int     path_absolute(const char *);
 int     stdfd_devnull(int, int, int);
 int     lib_contains_symbol(const char *, const char *);
+char   *get_homedir(void);
 
 void    sock_set_v6only(int);
 
@@ -231,6 +232,11 @@ int ptimeout_get_ms(struct timespec *pt);
 struct timespec *ptimeout_get_tsp(struct timespec *pt);
 int ptimeout_isset(struct timespec *pt);
 
+/* misc-agent.c */
+char   *agent_hostname_hash(void);
+int     agent_listener(const char *, const char *, int *, char **);
+void    agent_cleanup_stale(const char *, int);
+
 /* readpass.c */
 
 #define RP_ECHO                        0x0001
index 1158bec967810ac18106aecbec0de03d77b87196..e07395cb6f265a28aaf895872e60d2bcc52c7e4b 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: pathnames.h,v 1.32 2024/05/17 00:30:24 djm Exp $ */
+/* $OpenBSD: pathnames.h,v 1.34 2025/05/05 02:48:06 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  */
 #define _PATH_SSH_USER_DIR             ".ssh"
 
+
+/*
+ * The directory in which ssh-agent sockets and agent sockets forwarded by
+ * sshd reside. This directory should not be world-readable.
+ */
+#define _PATH_SSH_AGENT_SOCKET_DIR _PATH_SSH_USER_DIR "/agent"
+
 /*
  * Per-user file containing host keys of known hosts.  This file need not be
  * readable by anyone except the user him/herself, though this does not
index 6444c77f31c2c97dc598a31b7bcffc4a9559c209..630e0e6a353cf8b5a397bf33a136569b04446e35 100644 (file)
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.c,v 1.341 2025/04/09 07:00:03 djm Exp $ */
+/* $OpenBSD: session.c,v 1.342 2025/05/05 02:48:06 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -175,7 +175,6 @@ static char *auth_info_file = NULL;
 
 /* Name and directory of socket for authentication agent forwarding. */
 static char *auth_sock_name = NULL;
-static char *auth_sock_dir = NULL;
 
 /* removes the agent forwarding socket */
 
@@ -185,7 +184,6 @@ auth_sock_cleanup_proc(struct passwd *pw)
        if (auth_sock_name != NULL) {
                temporarily_use_uid(pw);
                unlink(auth_sock_name);
-               rmdir(auth_sock_dir);
                auth_sock_name = NULL;
                restore_uid();
        }
@@ -205,32 +203,15 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw)
        /* Temporarily drop privileged uid for mkdir/bind. */
        temporarily_use_uid(pw);
 
-       /* Allocate a buffer for the socket name, and format the name. */
-       auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX");
-
-       /* Create private directory for socket */
-       if (mkdtemp(auth_sock_dir) == NULL) {
+       if (agent_listener(pw->pw_dir, "sshd", &sock, &auth_sock_name) != 0) {
+               /* a more detailed error is already logged */
                ssh_packet_send_debug(ssh, "Agent forwarding disabled: "
-                   "mkdtemp() failed: %.100s", strerror(errno));
+                   "couldn't create listener socket");
                restore_uid();
-               free(auth_sock_dir);
-               auth_sock_dir = NULL;
                goto authsock_err;
        }
-
-       xasprintf(&auth_sock_name, "%s/agent.%ld",
-           auth_sock_dir, (long) getpid());
-
-       /* Start a Unix listener on auth_sock_name. */
-       sock = unix_listener(auth_sock_name, SSH_LISTEN_BACKLOG, 0);
-
-       /* Restore the privileged uid. */
        restore_uid();
 
-       /* Check for socket/bind/listen failure. */
-       if (sock < 0)
-               goto authsock_err;
-
        /* Allocate a channel for the authentication agent socket. */
        nc = channel_new(ssh, "auth-listener",
            SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1,
@@ -241,16 +222,9 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw)
 
  authsock_err:
        free(auth_sock_name);
-       if (auth_sock_dir != NULL) {
-               temporarily_use_uid(pw);
-               rmdir(auth_sock_dir);
-               restore_uid();
-               free(auth_sock_dir);
-       }
        if (sock != -1)
                close(sock);
        auth_sock_name = NULL;
-       auth_sock_dir = NULL;
        return 0;
 }
 
index 533ad6d3a6d24caf7b5808618ebe0563011bc0ac..3b515ac423849ab2d4e654135bbdbe26cb948dea 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: ssh-agent.1,v 1.82 2025/02/09 18:24:08 schwarze Exp $
+.\" $OpenBSD: ssh-agent.1,v 1.83 2025/05/05 02:48:06 djm Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: February 9 2025 $
+.Dd $Mdocdate: May 5 2025 $
 .Dt SSH-AGENT 1
 .Os
 .Sh NAME
 .Sh SYNOPSIS
 .Nm ssh-agent
 .Op Fl c | s
-.Op Fl \&Dd
+.Op Fl \&DdTU
 .Op Fl a Ar bind_address
 .Op Fl E Ar fingerprint_hash
 .Op Fl O Ar option
 .Op Fl P Ar allowed_providers
 .Op Fl t Ar life
 .Nm ssh-agent
+.Op Fl TU
 .Op Fl a Ar bind_address
 .Op Fl E Ar fingerprint_hash
 .Op Fl O Ar option
@@ -59,6 +60,8 @@
 .Nm ssh-agent
 .Op Fl c | s
 .Fl k
+.Nm ssh-agent
+.Fl u
 .Sh DESCRIPTION
 .Nm
 is a program to hold private keys used for public key authentication.
@@ -74,8 +77,8 @@ Bind the agent to the
 .Ux Ns -domain
 socket
 .Ar bind_address .
-The default is
-.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt .
+The default is to create a socket at a random path matching
+.Pa $HOME/.ssh/agent/s.*
 .It Fl c
 Generate C-shell commands on standard output.
 This is the default if
@@ -173,6 +176,11 @@ Generate Bourne shell commands on standard output.
 This is the default if
 .Ev SHELL
 does not look like it's a csh style of shell.
+.It Fl T
+Bind the agent socket in a randomised subdirectory of the form
+.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt ,
+instead of the default behaviour of using a randomised name matching
+.Pa $HOME/.ssh/agent/s.* .
 .It Fl t Ar life
 Set a default value for the maximum lifetime of identities added to the agent.
 The lifetime may be specified in seconds or in a time format specified in
@@ -186,6 +194,20 @@ If a command (and optional arguments) is given,
 this is executed as a subprocess of the agent.
 The agent exits automatically when the command given on the command
 line terminates.
+.It Fl U
+Instructs
+.Nm
+not to clean up stale agent sockets under
+.Pa $HOME/.ssh/agent/ .
+.It Fl u
+Instructs
+.Nm
+to only clean up stale agent sockets under
+.Pa $HOME/.ssh/agent/
+and then exit immediately.
+If this option is given twice,
+.Nm
+will delete stale agent sockets regardless of the host name that created them.
 .El
 .Pp
 There are three main ways to get an agent set up.
index 55b9f44f4d4ad126ded2604570f0f69ec3223aeb..8a88ef3fd1c0a9e763506677b05486b6f70e6e00 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-agent.c,v 1.311 2025/04/15 09:22:25 dtucker Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.312 2025/05/05 02:48:06 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2208,20 +2208,23 @@ static void
 usage(void)
 {
        fprintf(stderr,
-           "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n"
+           "usage: ssh-agent [-c | -s] [-DdTU] [-a bind_address] [-E fingerprint_hash]\n"
            "                 [-O option] [-P allowed_providers] [-t life]\n"
-           "       ssh-agent [-a bind_address] [-E fingerprint_hash] [-O option]\n"
+           "       ssh-agent [-TU] [-a bind_address] [-E fingerprint_hash] [-O option]\n"
            "                 [-P allowed_providers] [-t life] command [arg ...]\n"
-           "       ssh-agent [-c | -s] -k\n");
+           "       ssh-agent [-c | -s] -k\n"
+           "       ssh-agent -u\n");
        exit(1);
 }
 
 int
 main(int ac, char **av)
 {
-       int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0;
+       int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0;
+       int s_flag = 0, T_flag = 0, u_flag = 0, U_flag = 0;
        int sock = -1, ch, result, saved_errno;
-       char *shell, *format, *fdstr, *pidstr, *agentsocket = NULL;
+       char *homedir = NULL, *shell, *format, *pidstr, *agentsocket = NULL;
+       char *fdstr;
        const char *errstr = NULL;
        const char *ccp;
 #ifdef HAVE_SETRLIMIT
@@ -2256,7 +2259,7 @@ main(int ac, char **av)
        __progname = ssh_get_progname(av[0]);
        seed_rng();
 
-       while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) {
+       while ((ch = getopt(ac, av, "cDdksTuUE:a:O:P:t:")) != -1) {
                switch (ch) {
                case 'E':
                        fingerprint_hash = ssh_digest_alg_by_name(optarg);
@@ -2313,6 +2316,15 @@ main(int ac, char **av)
                                usage();
                        }
                        break;
+               case 'T':
+                       T_flag++;
+                       break;
+               case 'u':
+                       u_flag++;
+                       break;
+               case 'U':
+                       U_flag++;
+                       break;
                default:
                        usage();
                }
@@ -2320,9 +2332,14 @@ main(int ac, char **av)
        ac -= optind;
        av += optind;
 
-       if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag))
+       if (ac > 0 &&
+           (c_flag || k_flag || s_flag || d_flag || D_flag || u_flag))
                usage();
 
+       log_init(__progname,
+           d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO,
+           SYSLOG_FACILITY_AUTH, 1);
+
        if (allowed_providers == NULL)
                allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS);
        if (websafe_allowlist == NULL)
@@ -2358,6 +2375,14 @@ main(int ac, char **av)
                printf("echo Agent pid %ld killed;\n", (long)pid);
                exit(0);
        }
+       if (u_flag) {
+               if ((homedir = get_homedir()) == NULL)
+                       fatal("Couldn't determine home directory");
+               agent_cleanup_stale(homedir, u_flag > 1);
+               printf("Deleted stale agent sockets in ~/%s\n",
+                   _PATH_SSH_AGENT_SOCKET_DIR);
+               exit(0);
+       }
 
        /*
         * Minimum file descriptors:
@@ -2391,22 +2416,52 @@ main(int ac, char **av)
                sock = 3;
        }
 
-       /* Otherwise, create private directory for agent socket */
-       if (sock == -1) {
-               if (agentsocket == NULL) {
+       if (sock == -1 && agentsocket == NULL && !T_flag) {
+               /* Default case: ~/.ssh/agent/[socket] */
+               if ((homedir = get_homedir()) == NULL)
+                       fatal("Couldn't determine home directory");
+               if (!U_flag)
+                       agent_cleanup_stale(homedir, 0);
+               if (agent_listener(homedir, "agent", &sock, &agentsocket) != 0)
+                       fatal_f("Couldn't prepare agent socket");
+               if (strlcpy(socket_name, agentsocket,
+                   sizeof(socket_name)) >= sizeof(socket_name)) {
+                       fatal_f("Socket path \"%s\" too long",
+                           agentsocket);
+               }
+               free(homedir);
+               free(agentsocket);
+               agentsocket = NULL;
+       } else if (sock == -1) {
+               if (T_flag) {
+                       /*
+                        * Create private directory for agent socket
+                        * in $TMPDIR.
+                        */
                        mktemp_proto(socket_dir, sizeof(socket_dir));
                        if (mkdtemp(socket_dir) == NULL) {
                                perror("mkdtemp: private socket dir");
                                exit(1);
                        }
-                       snprintf(socket_name, sizeof socket_name,
-                          "%s/agent.%ld", socket_dir,
-                   (long)parent_pid);
+                       snprintf(socket_name, sizeof(socket_name),
+                           "%s/agent.%ld", socket_dir, (long)parent_pid);
                } else {
                        /* Try to use specified agent socket */
                        socket_dir[0] = '\0';
-                       strlcpy(socket_name, agentsocket, sizeof socket_name);
+                       if (strlcpy(socket_name, agentsocket,
+                          sizeof(socket_name)) >= sizeof(socket_name)) {
+                               fatal_f("Socket path \"%s\" too long",
+                                   agentsocket);
+                       }
+               }
+               /* Listen on socket */
+               prev_mask = umask(0177);
+               if ((sock = unix_listener(socket_name,
+                   SSH_LISTEN_BACKLOG, 0)) < 0) {
+                       *socket_name = '\0'; /* Don't unlink existing file */
+                       cleanup_exit(1);
                }
+               umask(prev_mask);
        }
 
        closefrom(sock == -1 ? STDERR_FILENO + 1 : sock + 1);