CVE-2016-10009: Untrusted Search Path (CWE-426)
value: Untrusted search path vulnerability in ssh-agent.c in ssh-agent in OpenSSH before 7.4 allows remote attackers to execute arbitrary local PKCS#11 modules by leveraging control over a forwarded agent-socket.
CWE description: The product searches for critical resources using an externally-supplied search path that can point to resources that are not under the product's direct control.
CVSS v3 base score: 7.3
Published: 2017-01-05T02:59Z
Repository: https://github.com/openbsd/src
Language: Other
Files changed: usr.bin/ssh/ssh-agent.1, usr.bin/ssh/ssh-agent.c
Commit date: 2016-11-30 03:07:37 +0000
Commit message: add a whitelist of paths from which ssh-agent will load (via
ssh-pkcs11-helper) a PKCS#11 module; ok markus@

Diff: diff --git a/usr.bin/ssh/ssh-agent.1 b/usr.bin/ssh/ssh-agent.1
index c4b50bbdfe7..372adbe7c7f 100644
--- a/usr.bin/ssh/ssh-agent.1
+++ b/usr.bin/ssh/ssh-agent.1
@@ -1,6 +1,6 @@
-.\" $OpenBSD: ssh-agent.1,v 1.62 2015/11/15 23:54:15 jmc Exp $
+.\" $OpenBSD: ssh-agent.1,v 1.63 2016/11/30 03:07:37 djm Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 .\"                    All rights reserved
 .\"
@@ -32,11 +32,11 @@
 .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 .\" (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: November 15 2015 $
+.Dd $Mdocdate: November 30 2016 $
 .Dt SSH-AGENT 1
 .Os
 .Sh NAME
 .Nm ssh-agent
 .Nd authentication agent
@@ -45,10 +45,11 @@
 .Op Fl c | s
 .Op Fl \&Dd
 .Op Fl a Ar bind_address
 .Op Fl E Ar fingerprint_hash
 .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
@@ -119,10 +120,22 @@ The default is
 .Dq sha256 .
 .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
diff --git a/usr.bin/ssh/ssh-agent.c b/usr.bin/ssh/ssh-agent.c
index 957ff5a9d60..4a91f36d3ff 100644
--- a/usr.bin/ssh/ssh-agent.c
+++ b/usr.bin/ssh/ssh-agent.c
@@ -1,6 +1,6 @@
-/* $OpenBSD: ssh-agent.c,v 1.214 2016/09/12 01:22:38 deraadt Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.215 2016/11/30 03:07:37 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
  * The authentication agent program.
@@ -67,15 +67,20 @@
 #include "compat.h"
 #include "log.h"
 #include "misc.h"
 #include "digest.h"
 #include "ssherr.h"
+#include "match.h"
 
 #ifdef ENABLE_PKCS11
 #include "ssh-pkcs11.h"
 #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;
@@ -119,10 +124,13 @@ pid_t cleanup_pid = 0;
 
 /* pathname and directory for AUTH_SOCKET */
 char socket_name[PATH_MAX];
 char socket_dir[PATH_MAX];
 
+/* 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;
@@ -722,11 +730,11 @@ no_identities(SocketEntry *e, u_int type)
 
 #ifdef ENABLE_PKCS11
 static void
 process_add_smartcard_key(SocketEntry *e)
 {
-	char *provider = NULL, *pin;
+	char *provider = NULL, *pin, canonical_provider[PATH_MAX];
 	int r, i, version, count = 0, success = 0, confirm = 0;
 	u_int seconds;
 	time_t death = 0;
 	u_char type;
 	struct sshkey **keys = NULL, *k;
@@ -754,23 +762,34 @@ process_add_smartcard_key(SocketEntry *e)
 			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, 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;
@@ -1155,11 +1174,11 @@ check_parent_exists(void)
 static void
 usage(void)
 {
 	fprintf(stderr,
 	    "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n"
-	    "                 [-t life] [command [arg ...]]\n"
+	    "                 [-P pkcs11_whitelist] [-t life] [command [arg ...]]\n"
 	    "       ssh-agent [-c | -s] -k\n");
 	exit(1);
 }
 
 int
@@ -1189,11 +1208,11 @@ main(int ac, char **av)
 
 #ifdef WITH_OPENSSL
 	OpenSSL_add_all_algorithms();
 #endif
 
-	while ((ch = getopt(ac, av, "cDdksE:a:t:")) != -1) {
+	while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) {
 		switch (ch) {
 		case 'E':
 			fingerprint_hash = ssh_digest_alg_by_name(optarg);
 			if (fingerprint_hash == -1)
 				fatal("Invalid hash algorithm \"%s\"", optarg);
@@ -1204,10 +1223,15 @@ main(int ac, char **av)
 			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;
@@ -1238,10 +1262,13 @@ main(int ac, char **av)
 	av += optind;
 
 	if (ac > 0 && (c_flag || k_flag || s_flag || d_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;
@@ -1383,11 +1410,11 @@ skip:
 	signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN);
 	signal(SIGHUP, cleanup_handler);
 	signal(SIGTERM, cleanup_handler);
 	nalloc = 0;
 
-	if (pledge("stdio cpath unix id proc exec", NULL) == -1)
+	if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1)
 		fatal("%s: pledge: %s", __progname, strerror(errno));
 
 	while (1) {
 		prepare_select(&readsetp, &writesetp, &max_fd, &nalloc, &tvp);
 		result = select(max_fd + 1, readsetp, writesetp, NULL, tvp);

Vulnerable code: .\" $OpenBSD: ssh-agent.1,v 1.62 2015/11/15 23:54:15 jmc Exp $
.Dd $Mdocdate: November 15 2015 $
/* $OpenBSD: ssh-agent.c,v 1.214 2016/09/12 01:22:38 deraadt Exp $ */
	char *provider = NULL, *pin;
	count = pkcs11_add_provider(provider, pin, &keys);
			id->provider = xstrdup(provider);
			id->comment = xstrdup(provider); /* XXX */
	    "                 [-t life] [command [arg ...]]\n"
	while ((ch = getopt(ac, av, "cDdksE:a:t:")) != -1) {
	if (pledge("stdio cpath unix id proc exec", NULL) == -1)
Fixed code: .\" $OpenBSD: ssh-agent.1,v 1.63 2016/11/30 03:07:37 djm Exp $
.Dd $Mdocdate: November 30 2016 $
.Op Fl P Ar pkcs11_whitelist
.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.
/* $OpenBSD: ssh-agent.c,v 1.215 2016/11/30 03:07:37 djm Exp $ */
#include "match.h"
#ifndef DEFAULT_PKCS11_WHITELIST
# define DEFAULT_PKCS11_WHITELIST "/usr/lib/*,/usr/local/lib/*"
#endif

/* PKCS#11 path whitelist */
static char *pkcs11_whitelist;

	char *provider = NULL, *pin, canonical_provider[PATH_MAX];
	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, 0) != 1) {
		verbose("refusing PKCS#11 add of \"%.100s\": "
		    "provider not whitelisted", canonical_provider);
		goto send;
	}
	debug("%s: add %.100s", __func__, canonical_provider);
	count = pkcs11_add_provider(canonical_provider, pin, &keys);
			id->provider = xstrdup(canonical_provider);
			id->comment = xstrdup(canonical_provider); /* XXX */
	    "                 [-P pkcs11_whitelist] [-t life] [command [arg ...]]\n"
	while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) {
		case 'P':
			if (pkcs11_whitelist != NULL)
				fatal("-P option already specified");
			pkcs11_whitelist = xstrdup(optarg);
			break;
	if (pkcs11_whitelist == NULL)
		pkcs11_whitelist = xstrdup(DEFAULT_PKCS11_WHITELIST);

	if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1)