# HG changeset patch
# Parent  57e3b5abff96c348922be4e98f8b24615bcd9186
whitelist paths for loading of PKCS#11 modules in ssh-agent

CVE-2016-10009
bsc#1016366

upstream commit 786d5994da79151180cb14a6cf157ebbba61c0cc

diff --git a/openssh-6.6p1/ssh-agent.1 b/openssh-6.6p1/ssh-agent.1
--- a/openssh-6.6p1/ssh-agent.1
+++ b/openssh-6.6p1/ssh-agent.1
@@ -41,16 +41,17 @@
 .Nm ssh-agent
 .Nd authentication agent
 .Sh SYNOPSIS
 .Nm ssh-agent
 .Op Fl c | s
 .Op Fl d
 .Op Fl a Ar bind_address
 .Op Fl t Ar life
+.Op Fl P Ar pkcs11_whitelist
 .Op Ar command Op Ar arg ...
 .Nm ssh-agent
 .Op Fl c | s
 .Fl k
 .Sh DESCRIPTION
 .Nm
 is a program to hold private keys used for public key authentication
 (RSA, DSA, ECDSA, ED25519).
@@ -83,16 +84,28 @@ looks like it's a csh style of shell.
 Debug mode.
 When this option is specified
 .Nm
 will not fork.
 .It Fl k
 Kill the current agent (given by the
 .Ev SSH_AGENT_PID
 environment variable).
+.It Fl P
+Specify a pattern-list of acceptable paths for PKCS#11 shared libraries
+that may be added using the
+.Fl s
+option to
+.Xr ssh-add 1 .
+The default is to allow loading PKCS#11 libraries from
+.Dq /usr/lib/*,/usr/local/lib/* .
+PKCS#11 libraries that do not match the whitelist will be refused.
+See PATTERNS in
+.Xr ssh_config 5
+for a description of pattern-list syntax.
 .It Fl s
 Generate Bourne shell commands on
 .Dv stdout .
 This is the default if
 .Ev SHELL
 does not look like it's a csh style of shell.
 .It Fl t Ar life
 Set a default value for the maximum lifetime of identities added to the agent.
diff --git a/openssh-6.6p1/ssh-agent.c b/openssh-6.6p1/ssh-agent.c
--- a/openssh-6.6p1/ssh-agent.c
+++ b/openssh-6.6p1/ssh-agent.c
@@ -70,25 +70,30 @@
 #include "rsa.h"
 #include "buffer.h"
 #include "key.h"
 #include "authfd.h"
 #include "compat.h"
 #include "log.h"
 #include "misc.h"
 #include "digest.h"
+#include "match.h"
 
 #ifdef ENABLE_PKCS11
 #include "ssh-pkcs11.h"
 #endif
 
 #if defined(HAVE_SYS_PRCTL_H)
 #include <sys/prctl.h>	/* For prctl() and PR_SET_DUMPABLE */
 #endif
 
+#ifndef DEFAULT_PKCS11_WHITELIST
+# define DEFAULT_PKCS11_WHITELIST "/usr/lib/*,/usr/local/lib/*"
+#endif
+
 typedef enum {
 	AUTH_UNUSED,
 	AUTH_SOCKET,
 	AUTH_CONNECTION
 } sock_type;
 
 typedef struct {
 	int fd;
@@ -123,16 +128,19 @@ int max_fd = 0;
 /* pid of shell == parent of agent */
 pid_t parent_pid = -1;
 time_t parent_alive_interval = 0;
 
 /* pathname and directory for AUTH_SOCKET */
 char socket_name[MAXPATHLEN];
 char socket_dir[MAXPATHLEN];
 
+/* PKCS#11 path whitelist */
+static char *pkcs11_whitelist;
+
 /* locking */
 #define LOCK_SIZE	32
 #define LOCK_SALT_SIZE	16
 #define LOCK_ROUNDS	1
 int locked = 0;
 char lock_passwd[LOCK_SIZE];
 char lock_salt[LOCK_SALT_SIZE];
 
@@ -611,17 +619,17 @@ no_identities(SocketEntry *e, u_int type
 	buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg));
 	buffer_free(&msg);
 }
 
 #ifdef ENABLE_PKCS11
 static void
 process_add_smartcard_key(SocketEntry *e)
 {
-	char *provider = NULL, *pin;
+	char *provider = NULL, *pin, canonical_provider[PATH_MAX];
 	int i, type, version, count = 0, success = 0, confirm = 0;
 	time_t death = 0;
 	Key **keys = NULL, *k;
 	Identity *id;
 	Idtab *tab;
 
 	provider = buffer_get_string(&e->request, NULL);
 	pin = buffer_get_string(&e->request, NULL);
@@ -635,29 +643,41 @@ process_add_smartcard_key(SocketEntry *e
 			confirm = 1;
 			break;
 		default:
 			error("process_add_smartcard_key: "
 			    "Unknown constraint type %d", type);
 			goto send;
 		}
 	}
+	if (realpath(provider, canonical_provider) == NULL) {
+		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+		    provider, strerror(errno));
+		goto send;
+	}
+	if (match_pattern_list(canonical_provider, pkcs11_whitelist,
+		    strlen(pkcs11_whitelist), 0) != 1) {
+		verbose("refusing PKCS#11 add of \"%.100s\": "
+		    "provider not whitelisted", canonical_provider);
+		goto send;
+	}
+	debug("%s: add %.100s", __func__, canonical_provider);
 	if (lifetime && !death)
 		death = monotime() + lifetime;
 
-	count = pkcs11_add_provider(provider, pin, &keys);
+	count = pkcs11_add_provider(canonical_provider, pin, &keys);
 	for (i = 0; i < count; i++) {
 		k = keys[i];
 		version = k->type == KEY_RSA1 ? 1 : 2;
 		tab = idtab_lookup(version);
 		if (lookup_identity(k, version) == NULL) {
 			id = xcalloc(1, sizeof(Identity));
 			id->key = k;
-			id->provider = xstrdup(provider);
-			id->comment = xstrdup(provider); /* XXX */
+			id->provider = xstrdup(canonical_provider);
+			id->comment = xstrdup(canonical_provider); /* XXX */
 			id->death = death;
 			id->confirm = confirm;
 			TAILQ_INSERT_TAIL(&tab->idlist, id, next);
 			tab->nentries++;
 			success = 1;
 		} else {
 			key_free(k);
 		}
@@ -1031,16 +1051,18 @@ usage(void)
 	    __progname);
 	fprintf(stderr, "Options:\n");
 	fprintf(stderr, "  -c          Generate C-shell commands on stdout.\n");
 	fprintf(stderr, "  -s          Generate Bourne shell commands on stdout.\n");
 	fprintf(stderr, "  -k          Kill the current agent.\n");
 	fprintf(stderr, "  -d          Debug mode.\n");
 	fprintf(stderr, "  -a socket   Bind agent socket to given name.\n");
 	fprintf(stderr, "  -t life     Default identity lifetime (seconds).\n");
+	fprintf(stderr, "  -P pkcs11_whitelist\n");
+    fprintf(stderr, "              whitelist of directories for loading PKCS#11 modules from.\n");
 	exit(1);
 }
 
 int
 main(int ac, char **av)
 {
 	int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0;
 	int sock, fd, ch, result, saved_errno;
@@ -1071,26 +1093,31 @@ main(int ac, char **av)
 	prctl(PR_SET_DUMPABLE, 0);
 #endif
 
 	OpenSSL_add_all_algorithms();
 
 	__progname = ssh_get_progname(av[0]);
 	seed_rng();
 
-	while ((ch = getopt(ac, av, "cdksa:t:")) != -1) {
+	while ((ch = getopt(ac, av, "cdksa:P:t:")) != -1) {
 		switch (ch) {
 		case 'c':
 			if (s_flag)
 				usage();
 			c_flag++;
 			break;
 		case 'k':
 			k_flag++;
 			break;
+		case 'P':
+			if (pkcs11_whitelist != NULL)
+				fatal("-P option already specified");
+			pkcs11_whitelist = xstrdup(optarg);
+			break;
 		case 's':
 			if (c_flag)
 				usage();
 			s_flag++;
 			break;
 		case 'd':
 			if (d_flag)
 				usage();
@@ -1110,16 +1137,19 @@ main(int ac, char **av)
 		}
 	}
 	ac -= optind;
 	av += optind;
 
 	if (ac > 0 && (c_flag || k_flag || s_flag || d_flag))
 		usage();
 
+	if (pkcs11_whitelist == NULL)
+		pkcs11_whitelist = xstrdup(DEFAULT_PKCS11_WHITELIST);
+
 	if (ac == 0 && !c_flag && !s_flag) {
 		shell = getenv("SHELL");
 		if (shell != NULL && (len = strlen(shell)) > 2 &&
 		    strncmp(shell + len - 3, "csh", 3) == 0)
 			c_flag = 1;
 	}
 	if (k_flag) {
 		const char *errstr = NULL;
