CVE-2023-51384: Insufficient Information (NVD-CWE-noinfo)
value: In ssh-agent in OpenSSH before 9.6, certain destination constraints can be incompletely applied. When destination constraints are specified during addition of PKCS#11-hosted private keys, these constraints are only applied to the first key, even if a PKCS#11 token returns multiple keys.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.5
Published: 2023-12-18T19:15Z
Repository: https://github.com/openssh/openssh-portable
Language: C
Files changed: ssh-agent.c
Commit date: 2023-12-19 01:51:46 +1100
Commit message: upstream: apply destination constraints to all p11 keys

Previously applied only to the first key returned from each token.

ok markus@

OpenBSD-Commit-ID: 36df3afb8eb94eec6b2541f063d0d164ef8b488d

Diff: diff --git a/ssh-agent.c b/ssh-agent.c
index f528611635..1d4c321eb0 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -1,6 +1,6 @@
-/* $OpenBSD: ssh-agent.c,v 1.300 2023/07/19 13:56:33 djm Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.301 2023/12/18 14:46:12 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.
@@ -245,10 +245,95 @@ free_dest_constraints(struct dest_constraint *dcs, size_t ndcs)
 		free_dest_constraint_hop(&dcs[i].to);
 	}
 	free(dcs);
 }
 
+static void
+dup_dest_constraint_hop(const struct dest_constraint_hop *dch,
+    struct dest_constraint_hop *out)
+{
+	u_int i;
+	int r;
+
+	out->user = dch->user == NULL ? NULL : xstrdup(dch->user);
+	out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname);
+	out->is_ca = dch->is_ca;
+	out->nkeys = dch->nkeys;
+	out->keys = out->nkeys == 0 ? NULL :
+	    xcalloc(out->nkeys, sizeof(*out->keys));
+	out->key_is_ca = out->nkeys == 0 ? NULL :
+	    xcalloc(out->nkeys, sizeof(*out->key_is_ca));
+	for (i = 0; i < dch->nkeys; i++) {
+		if (dch->keys[i] != NULL &&
+		    (r = sshkey_from_private(dch->keys[i],
+		    &(out->keys[i]))) != 0)
+			fatal_fr(r, "copy key");
+		out->key_is_ca[i] = dch->key_is_ca[i];
+	}
+}
+
+static struct dest_constraint *
+dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs)
+{
+	size_t i;
+	struct dest_constraint *ret;
+
+	if (ndcs == 0)
+		return NULL;
+	ret = xcalloc(ndcs, sizeof(*ret));
+	for (i = 0; i < ndcs; i++) {
+		dup_dest_constraint_hop(&dcs[i].from, &ret[i].from);
+		dup_dest_constraint_hop(&dcs[i].to, &ret[i].to);
+	}
+	return ret;
+}
+
+#ifdef DEBUG_CONSTRAINTS
+static void
+dump_dest_constraint_hop(const struct dest_constraint_hop *dch)
+{
+	u_int i;
+	char *fp;
+
+	debug_f("user %s hostname %s is_ca %d nkeys %u",
+	    dch->user == NULL ? "(null)" : dch->user,
+	    dch->hostname == NULL ? "(null)" : dch->hostname,
+	    dch->is_ca, dch->nkeys);
+	for (i = 0; i < dch->nkeys; i++) {
+		fp = NULL;
+		if (dch->keys[i] != NULL &&
+		    (fp = sshkey_fingerprint(dch->keys[i],
+		    SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
+			fatal_f("fingerprint failed");
+		debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys,
+		    dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]),
+		    dch->keys[i] == NULL ? "" : " ",
+		    dch->keys[i] == NULL ? "none" : fp,
+		    dch->key_is_ca[i]);
+		free(fp);
+	}
+}
+#endif /* DEBUG_CONSTRAINTS */
+
+static void
+dump_dest_constraints(const char *context,
+    const struct dest_constraint *dcs, size_t ndcs)
+{
+#ifdef DEBUG_CONSTRAINTS
+	size_t i;
+
+	debug_f("%s: %zu constraints", context, ndcs);
+	for (i = 0; i < ndcs; i++) {
+		debug_f("constraint %zu / %zu: from: ", i, ndcs);
+		dump_dest_constraint_hop(&dcs[i].from);
+		debug_f("constraint %zu / %zu: to: ", i, ndcs);
+		dump_dest_constraint_hop(&dcs[i].to);
+	}
+	debug_f("done for %s", context);
+#endif /* DEBUG_CONSTRAINTS */
+}
+
 static void
 free_identity(Identity *id)
 {
 	sshkey_free(id->key);
 	free(id->provider);
@@ -516,17 +601,26 @@ static void
 process_request_identities(SocketEntry *e)
 {
 	Identity *id;
 	struct sshbuf *msg, *keys;
 	int r;
-	u_int nentries = 0;
+	u_int i = 0, nentries = 0;
+	char *fp;
 
 	debug2_f("entering");
 
 	if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL)
 		fatal_f("sshbuf_new failed");
 	TAILQ_FOREACH(id, &idtab->idlist, next) {
+		if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT,
+		    SSH_FP_DEFAULT)) == NULL)
+			fatal_f("fingerprint failed");
+		debug_f("key %u / %u: %s %s", i++, idtab->nentries,
+		    sshkey_ssh_name(id->key), fp);
+		dump_dest_constraints(__func__,
+		    id->dest_constraints, id->ndest_constraints);
+		free(fp);
 		/* identity not visible, don't include in response */
 		if (identity_permitted(id, e, NULL, NULL, NULL) != 0)
 			continue;
 		if ((r = sshkey_puts_opts(id->key, keys,
 		    SSHKEY_SERIALIZE_INFO)) != 0 ||
@@ -1222,10 +1316,11 @@ process_add_identity(SocketEntry *e)
 	    &sk_provider, &dest_constraints, &ndest_constraints) != 0) {
 		error_f("failed to parse constraints");
 		sshbuf_reset(e->request);
 		goto out;
 	}
+	dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
 
 	if (sk_provider != NULL) {
 		if (!sshkey_is_sk(k)) {
 			error("Cannot add provider: %s is not an "
 			    "authenticator-hosted key", sshkey_type(k));
@@ -1401,10 +1496,11 @@ process_add_smartcard_key(SocketEntry *e)
 	if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm,
 	    NULL, &dest_constraints, &ndest_constraints) != 0) {
 		error_f("failed to parse constraints");
 		goto send;
 	}
+	dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
 	if (e->nsession_ids != 0 && !remote_add_provider) {
 		verbose("failed PKCS#11 add of \"%.100s\": remote addition of "
 		    "providers is disabled", provider);
 		goto send;
 	}
@@ -1436,14 +1532,13 @@ process_add_smartcard_key(SocketEntry *e)
 			} else {
 				id->comment = xstrdup(canonical_provider);
 			}
 			id->death = death;
 			id->confirm = confirm;
-			id->dest_constraints = dest_constraints;
+			id->dest_constraints = dup_dest_constraints(
+			    dest_constraints, ndest_constraints);
 			id->ndest_constraints = ndest_constraints;
-			dest_constraints = NULL; /* transferred */
-			ndest_constraints = 0;
 			TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
 			idtab->nentries++;
 			success = 1;
 		}
 		/* XXX update constraints for existing keys */

Vulnerable code: /* $OpenBSD: ssh-agent.c,v 1.300 2023/07/19 13:56:33 djm Exp $ */
	u_int nentries = 0;
			id->dest_constraints = dest_constraints;
			dest_constraints = NULL; /* transferred */
			ndest_constraints = 0;
Fixed code: /* $OpenBSD: ssh-agent.c,v 1.301 2023/12/18 14:46:12 djm Exp $ */
static void
dup_dest_constraint_hop(const struct dest_constraint_hop *dch,
    struct dest_constraint_hop *out)
{
	u_int i;
	int r;

	out->user = dch->user == NULL ? NULL : xstrdup(dch->user);
	out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname);
	out->is_ca = dch->is_ca;
	out->nkeys = dch->nkeys;
	out->keys = out->nkeys == 0 ? NULL :
	    xcalloc(out->nkeys, sizeof(*out->keys));
	out->key_is_ca = out->nkeys == 0 ? NULL :
	    xcalloc(out->nkeys, sizeof(*out->key_is_ca));
	for (i = 0; i < dch->nkeys; i++) {
		if (dch->keys[i] != NULL &&
		    (r = sshkey_from_private(dch->keys[i],
		    &(out->keys[i]))) != 0)
			fatal_fr(r, "copy key");
		out->key_is_ca[i] = dch->key_is_ca[i];
	}
}

static struct dest_constraint *
dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs)
{
	size_t i;
	struct dest_constraint *ret;

	if (ndcs == 0)
		return NULL;
	ret = xcalloc(ndcs, sizeof(*ret));
	for (i = 0; i < ndcs; i++) {
		dup_dest_constraint_hop(&dcs[i].from, &ret[i].from);
		dup_dest_constraint_hop(&dcs[i].to, &ret[i].to);
	}
	return ret;
}

#ifdef DEBUG_CONSTRAINTS
static void
dump_dest_constraint_hop(const struct dest_constraint_hop *dch)
{
	u_int i;
	char *fp;

	debug_f("user %s hostname %s is_ca %d nkeys %u",
	    dch->user == NULL ? "(null)" : dch->user,
	    dch->hostname == NULL ? "(null)" : dch->hostname,
	    dch->is_ca, dch->nkeys);
	for (i = 0; i < dch->nkeys; i++) {
		fp = NULL;
		if (dch->keys[i] != NULL &&
		    (fp = sshkey_fingerprint(dch->keys[i],
		    SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
			fatal_f("fingerprint failed");
		debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys,
		    dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]),
		    dch->keys[i] == NULL ? "" : " ",
		    dch->keys[i] == NULL ? "none" : fp,
		    dch->key_is_ca[i]);
		free(fp);
	}
}
#endif /* DEBUG_CONSTRAINTS */

static void
dump_dest_constraints(const char *context,
    const struct dest_constraint *dcs, size_t ndcs)
{
#ifdef DEBUG_CONSTRAINTS
	size_t i;

	debug_f("%s: %zu constraints", context, ndcs);
	for (i = 0; i < ndcs; i++) {
		debug_f("constraint %zu / %zu: from: ", i, ndcs);
		dump_dest_constraint_hop(&dcs[i].from);
		debug_f("constraint %zu / %zu: to: ", i, ndcs);
		dump_dest_constraint_hop(&dcs[i].to);
	}
	debug_f("done for %s", context);
#endif /* DEBUG_CONSTRAINTS */
}

	u_int i = 0, nentries = 0;
	char *fp;
		if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT,
		    SSH_FP_DEFAULT)) == NULL)
			fatal_f("fingerprint failed");
		debug_f("key %u / %u: %s %s", i++, idtab->nentries,
		    sshkey_ssh_name(id->key), fp);
		dump_dest_constraints(__func__,
		    id->dest_constraints, id->ndest_constraints);
		free(fp);
	dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
	dump_dest_constraints(__func__, dest_constraints, ndest_constraints);
			id->dest_constraints = dup_dest_constraints(
			    dest_constraints, ndest_constraints);