package/openssh: patch various CVEs

Thanks to the Debian community to providing the patches. This commit
fixes the following vulnerability.

- CVE-2025-61984:
    ssh in OpenSSH before 10.1 allows control characters in usernames that
    originate from certain possibly untrusted sources, potentially leading
    to code execution when a ProxyCommand is used. The untrusted sources
    are the command line and %-sequence expansion of a configuration file.
    (A configuration file that provides a complete literal username is not
    categorized as an untrusted source.)
    https://www.cve.org/CVERecord?id=CVE-2025-61984

- CVE-2025-61985:
    ssh in OpenSSH before 10.1 allows the '\0' character in an ssh:// URI,
    potentially leading to code execution when a ProxyCommand is used.
    https://www.cve.org/CVERecord?id=CVE-2025-61985

- CVE-2026-35385:
    In OpenSSH before 10.3, a file downloaded by scp may be installed
    setuid or setgid, an outcome contrary to some users' expectations, if
    the download is performed as root with -O (legacy scp protocol) and
    without -p (preserve mode).
    https://www.cve.org/CVERecord?id=CVE-2026-35385

- CVE-2026-35386:
    In OpenSSH before 10.3, command execution can occur via shell
    metacharacters in a username within a command line. This requires a
    scenario where the username on the command line is untrusted, and also
    requires a non-default configurations of % in ssh_config.
    https://www.cve.org/CVERecord?id=CVE-2026-35386

- CVE-2026-35387:
    OpenSSH before 10.3 can use unintended ECDSA algorithms. Listing of
    any ECDSA algorithm in PubkeyAcceptedAlgorithms or
    HostbasedAcceptedAlgorithms is misinterpreted to mean all ECDSA
    algorithms.
    https://www.cve.org/CVERecord?id=CVE-2026-35387

- CVE-2026-35388:
    OpenSSH before 10.3 omits connection multiplexing confirmation for
    proxy-mode multiplexing sessions.
    https://www.cve.org/CVERecord?id=CVE-2026-35388

- CVE-2026-35414:
    OpenSSH before 10.3 mishandles the authorized_keys principals option
    in uncommon scenarios involving a principals list in conjunction with
    a Certificate Authority that makes certain use of comma characters.
    https://www.cve.org/CVERecord?id=CVE-2026-35414

Signed-off-by: Thomas Perale <thomas.perale@mind.be>
This commit is contained in:
Thomas Perale
2026-05-29 16:31:10 +02:00
parent ef93b47d98
commit c84ebd03a5
11 changed files with 929 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
From f17eedbd2398f00fbb96170b60c8d2895318223e Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 4 Sep 2025 03:04:44 +0000
Subject: Add more username validity checks
[cjwatson: Reduced from a more extensive upstream change, since OpenSSH
< 10.0 doesn't support %-expansion of usernames.]
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2025-61984-tests.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=f64701ca25795548a61614d0b13391d6dfa7f38c
CVE: CVE-2025-61984
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
regress/percent.sh | 37 +++++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/regress/percent.sh b/regress/percent.sh
index 3dfa8d2df..6b9c492b0 100644
--- a/regress/percent.sh
+++ b/regress/percent.sh
@@ -29,6 +29,20 @@ trial()
somehost true
got=`cat $OBJ/actual`
;;
+ user)
+ got=`${SSH} -F $OBJ/ssh_proxy -o $opt="$arg" -G \
+ remuser@somehost | awk '$1=="'$opt'"{print $2}'`
+ ;;
+ user-l)
+ # Also test ssh -l
+ got=`${SSH} -F $OBJ/ssh_proxy -l "$arg" -G \
+ somehost | awk '$1=="'user'"{print $2}'`
+ ;;
+ user-at)
+ # Also test user@host
+ got=`${SSH} -F $OBJ/ssh_proxy -G "$arg@somehost" | \
+ awk '$1=="'user'"{print $2}'`
+ ;;
userknownhostsfile)
# Move the userknownhosts file to what the expansion says,
# make sure ssh works then put it back.
@@ -107,11 +121,11 @@ done
# Subset of above since we don't expand shell-style variables on anything that
# runs a command because the shell will expand those.
+FOO=bar
+export FOO
for i in controlpath identityagent forwardagent localforward remoteforward \
userknownhostsfile; do
verbose $tid $i dollar
- FOO=bar
- export FOO
trial $i '${FOO}' $FOO
done
@@ -122,3 +136,22 @@ for i in controlpath identityagent forwardagent; do
trial $i '~' $HOME/
trial $i '~/.ssh' $HOME/.ssh
done
+
+# These should be not be expanded but rejected for containing shell characters.
+verbose $tid user-l noenv
+${SSH} -F $OBJ/ssh_proxy -l '${FOO}' -G somehost && fail "user-l expanded env"
+verbose $tid user-at noenv
+${SSH} -F $OBJ/ssh_proxy -G '${FOO}@somehost' && fail "user-at expanded env"
+
+FOO=`printf 'x\ay'`
+export FOO
+
+# These should be rejected as containing control characters.
+verbose $tid user-l badchar
+${SSH} -F $OBJ/ssh_proxy -l "${FOO}" -G somehost && fail "user-l expanded env"
+verbose $tid user-at badchar
+${SSH} -F $OBJ/ssh_proxy -G "${FOO}@somehost" && fail "user-at expanded env"
+
+# Literal control characters in config is acceptable
+verbose $tid user control-literal
+trial user "$FOO" "$FOO"

View File

@@ -0,0 +1,112 @@
From 8d3eae0cb6c443f5b3747aedde770fbeee7317f9 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 4 Sep 2025 00:29:09 +0000
Subject: Refuse usernames that include control characters
Since OpenSSH 9.6, all usernames have been subject to validity checking.
This change tightens the validity checks by refusing usernames that
include control characters; these can cause surprises when supplied
adversarially.
This change also relaxes the validity checks in one small way: usernames
supplied via the configuration file as literals are not subject to these
validity checks. This allows usernames that contain arbitrary characters
to be used, but only via configuration files. This is done on the basis
that ssh's configuration is trusted.
Pointed out by David Leadbeater, ok deraadt@
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2025-61984.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=35d5917652106aede47621bb3f64044604164043
CVE: CVE-2025-61984
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
ssh.c | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/ssh.c b/ssh.c
index 48d93ddf2..9c49f98a8 100644
--- a/ssh.c
+++ b/ssh.c
@@ -649,6 +649,8 @@ valid_ruser(const char *s)
if (*s == '-')
return 0;
for (i = 0; s[i] != 0; i++) {
+ if (iscntrl((u_char)s[i]))
+ return 0;
if (strchr("'`\";&<>|(){}", s[i]) != NULL)
return 0;
/* Disallow '-' after whitespace */
@@ -671,6 +673,7 @@ main(int ac, char **av)
int i, r, opt, exit_status, use_syslog, direct, timeout_ms;
int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0;
char *p, *cp, *line, *argv0, *logfile;
+ int user_on_commandline = 0, user_was_default = 0;
char cname[NI_MAXHOST], thishost[NI_MAXHOST];
struct stat st;
struct passwd *pw;
@@ -1016,8 +1019,10 @@ main(int ac, char **av)
}
break;
case 'l':
- if (options.user == NULL)
+ if (options.user == NULL) {
options.user = optarg;
+ user_on_commandline = 1;
+ }
break;
case 'L':
@@ -1120,6 +1125,7 @@ main(int ac, char **av)
if (options.user == NULL) {
options.user = tuser;
tuser = NULL;
+ user_on_commandline = 1;
}
free(tuser);
if (options.port == -1 && tport != -1)
@@ -1134,6 +1140,7 @@ main(int ac, char **av)
if (options.user == NULL) {
options.user = p;
p = NULL;
+ user_on_commandline = 1;
}
*cp++ = '\0';
host = xstrdup(cp);
@@ -1288,8 +1295,10 @@ main(int ac, char **av)
if (fill_default_options(&options) != 0)
cleanup_exit(255);
- if (options.user == NULL)
+ if (options.user == NULL) {
+ user_was_default = 1;
options.user = xstrdup(pw->pw_name);
+ }
/*
* If ProxyJump option specified, then construct a ProxyCommand now.
@@ -1430,11 +1439,22 @@ main(int ac, char **av)
options.host_key_alias : options.host_arg);
cinfo->host_arg = xstrdup(options.host_arg);
cinfo->remhost = xstrdup(host);
- cinfo->remuser = xstrdup(options.user);
cinfo->homedir = xstrdup(pw->pw_dir);
cinfo->locuser = xstrdup(pw->pw_name);
cinfo->jmphost = xstrdup(options.jump_host == NULL ?
"" : options.jump_host);
+
+ /*
+ * Usernames specified on the commandline must be validated.
+ * Conversely, usernames from getpwnam(3) or specified as literals
+ * via configuration (i.e. not expanded) are not subject to validation.
+ */
+ if (user_on_commandline && !valid_ruser(options.user))
+ fatal("remote username contains invalid characters");
+
+ /* Store it and calculate hash. */
+ cinfo->remuser = xstrdup(options.user);
+
cinfo->conn_hash_hex = ssh_connection_hash(cinfo->thishost,
cinfo->remhost, cinfo->portstr, cinfo->remuser, cinfo->jmphost);

View File

@@ -0,0 +1,40 @@
From 2ada375659b2c3d1f85739bc1ceaefb9f9128600 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 4 Sep 2025 00:30:06 +0000
Subject: upstream: don't allow \0 characters in url-encoded strings.
Suggested by David Leadbeater, ok deraadt@
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2025-61985.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=43b3bff47bb029f2299bacb6a36057981b39fdb0
CVE: CVE-2025-61985
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
misc.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/misc.c b/misc.c
index 36e72f5b5..a50119446 100644
--- a/misc.c
+++ b/misc.c
@@ -990,7 +990,7 @@ urldecode(const char *src)
size_t srclen;
if ((srclen = strlen(src)) >= SIZE_MAX)
- fatal_f("input too large");
+ return NULL;
ret = xmalloc(srclen + 1);
for (dst = ret; *src != '\0'; src++) {
switch (*src) {
@@ -998,9 +998,10 @@ urldecode(const char *src)
*dst++ = ' ';
break;
case '%':
+ /* note: don't allow \0 characters */
if (!isxdigit((unsigned char)src[1]) ||
!isxdigit((unsigned char)src[2]) ||
- (ch = hexchar(src + 1)) == -1) {
+ (ch = hexchar(src + 1)) == -1 || ch == 0) {
free(ret);
return NULL;
}

View File

@@ -0,0 +1,36 @@
From d0d5ee1547b4fc7a3b355d15262f272682465a2f Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:42:16 +0000
Subject: upstream: when downloading files as root in legacy (-O) mode and
without the -p (preserve modes) flag set, clear setuid/setgid bits from
downloaded files as one might expect.
AFAIK this bug dates back to the original Berkeley rcp program.
Reported by Christos Papakonstantinou of Cantina and Spearbit.
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35385.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=487e8ac146f7d6616f65c125d5edb210519b833a
CVE: CVE-2026-35385
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
scp.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scp.c b/scp.c
index df590c4e3..77c854002 100644
--- a/scp.c
+++ b/scp.c
@@ -1684,8 +1684,10 @@ sink(int argc, char **argv, const char *src)
setimes = targisdir = 0;
mask = umask(0);
- if (!pflag)
+ if (!pflag) {
+ mask |= 07000;
(void) umask(mask);
+ }
if (argc != 1) {
run_err("ambiguous target");
exit(1);

View File

@@ -0,0 +1,318 @@
From 03dbf03cf10c5ea273d909caefa3a105824a9ea0 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 30 Mar 2026 07:18:24 +0000
Subject: upstream: apply the same validity rules to usernames and hostnames
set for ProxyJump/-J on the commandline as we do for destination user/host
names.
Specifically, they are no longer allowed to contain most characters
that have special meaning for common shells. Special characters are
still allowed in ProxyJump commands that are specified in the config
files.
This _reduces_ the chance that shell characters from a hostile -J
option from ending up in a shell execution context.
Don't pass untrusted stuff to the ssh commandline, it's not intended
to be a security boundary. We try to make it safe where we can, but
we can't make guarantees, because we can't know the parsing rules
and special characters for all the shells in the world, nor can we
know what the user does with this data in their ssh_config wrt
percent expansion, LocalCommand, match exec, etc.
While I'm in there, make ProxyJump and ProxyCommand first-match-wins
between each other.
reported by rabbit; ok dtucker@
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35386-1.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=0a0ef4515361143cad21afa072319823854c1cf6
CVE: CVE-2026-35386
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
readconf.c | 124 +++++++++++++++++++++++++++++++++++++----------------
readconf.h | 4 +-
ssh.c | 48 +++------------------
3 files changed, 95 insertions(+), 81 deletions(-)
diff --git a/readconf.c b/readconf.c
index 2db9cb6ae..a6e8303ff 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1483,9 +1483,6 @@ parse_char_array:
case oProxyCommand:
charptr = &options->proxy_command;
- /* Ignore ProxyCommand if ProxyJump already specified */
- if (options->jump_host != NULL)
- charptr = &options->jump_host; /* Skip below */
parse_command:
if (str == NULL) {
error("%.200s line %d: Missing argument.",
@@ -1506,7 +1503,7 @@ parse_command:
}
len = strspn(str, WHITESPACE "=");
/* XXX use argv? */
- if (parse_jump(str + len, options, *activep) == -1) {
+ if (parse_jump(str + len, options, cmdline, *activep) == -1) {
error("%.200s line %d: Invalid ProxyJump \"%s\"",
filename, linenum, str + len);
goto out;
@@ -3298,65 +3295,116 @@ parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remo
}
int
-parse_jump(const char *s, Options *o, int active)
+ssh_valid_hostname(const char *s)
{
- char *orig, *sdup, *cp;
- char *host = NULL, *user = NULL;
- int r, ret = -1, port = -1, first;
+ size_t i;
- active &= o->proxy_command == NULL && o->jump_host == NULL;
+ if (*s == '-')
+ return 0;
+ for (i = 0; s[i] != 0; i++) {
+ if (strchr("'`\"$\\;&<>|(){},", s[i]) != NULL ||
+ isspace((u_char)s[i]) || iscntrl((u_char)s[i]))
+ return 0;
+ }
+ return 1;
+}
- orig = sdup = xstrdup(s);
+int
+ssh_valid_ruser(const char *s)
+{
+ size_t i;
- /* Remove comment and trailing whitespace */
+ if (*s == '-')
+ return 0;
+ for (i = 0; s[i] != 0; i++) {
+ if (iscntrl((u_char)s[i]))
+ return 0;
+ if (strchr("'`\";&<>|(){}", s[i]) != NULL)
+ return 0;
+ /* Disallow '-' after whitespace */
+ if (isspace((u_char)s[i]) && s[i + 1] == '-')
+ return 0;
+ /* Disallow \ in last position */
+ if (s[i] == '\\' && s[i + 1] == '\0')
+ return 0;
+ }
+ return 1;
+}
+
+int
+parse_jump(const char *s, Options *o, int strict, int active)
+{
+ char *orig = NULL, *sdup = NULL, *cp;
+ char *tmp_user = NULL, *tmp_host = NULL, *host = NULL, *user = NULL;
+ int r, ret = -1, tmp_port = -1, port = -1, first = 1;
+
+ if (strcasecmp(s, "none") == 0) {
+ if (active && o->jump_host == NULL) {
+ o->jump_host = xstrdup("none");
+ o->jump_port = 0;
+ }
+ return 0;
+ }
+
+ orig = xstrdup(s);
if ((cp = strchr(orig, '#')) != NULL)
*cp = '\0';
rtrim(orig);
- first = active;
+ active &= o->proxy_command == NULL && o->jump_host == NULL;
+ sdup = xstrdup(orig);
do {
- if (strcasecmp(s, "none") == 0)
- break;
+ /* Work backwards through string */
if ((cp = strrchr(sdup, ',')) == NULL)
cp = sdup; /* last */
else
*cp++ = '\0';
+ r = parse_ssh_uri(cp, &tmp_user, &tmp_host, &tmp_port);
+ if (r == -1 || (r == 1 && parse_user_host_port(cp,
+ &tmp_user, &tmp_host, &tmp_port) != 0))
+ goto out; /* error already logged */
+ if (strict) {
+ if (!ssh_valid_hostname(tmp_host)) {
+ error_f("invalid hostname \"%s\"", tmp_host);
+ goto out;
+ }
+ if (tmp_user != NULL && !ssh_valid_ruser(tmp_user)) {
+ error_f("invalid username \"%s\"", tmp_user);
+ goto out;
+ }
+ }
if (first) {
- /* First argument and configuration is active */
- r = parse_ssh_uri(cp, &user, &host, &port);
- if (r == -1 || (r == 1 &&
- parse_user_host_port(cp, &user, &host, &port) != 0))
- goto out;
- } else {
- /* Subsequent argument or inactive configuration */
- r = parse_ssh_uri(cp, NULL, NULL, NULL);
- if (r == -1 || (r == 1 &&
- parse_user_host_port(cp, NULL, NULL, NULL) != 0))
- goto out;
+ user = tmp_user;
+ host = tmp_host;
+ port = tmp_port;
+ tmp_user = tmp_host = NULL; /* transferred */
}
first = 0; /* only check syntax for subsequent hosts */
+ free(tmp_user);
+ free(tmp_host);
+ tmp_user = tmp_host = NULL;
+ tmp_port = -1;
} while (cp != sdup);
+
/* success */
if (active) {
- if (strcasecmp(s, "none") == 0) {
- o->jump_host = xstrdup("none");
- o->jump_port = 0;
- } else {
- o->jump_user = user;
- o->jump_host = host;
- o->jump_port = port;
- o->proxy_command = xstrdup("none");
- user = host = NULL;
- if ((cp = strrchr(s, ',')) != NULL && cp != s) {
- o->jump_extra = xstrdup(s);
- o->jump_extra[cp - s] = '\0';
- }
+ o->jump_user = user;
+ o->jump_host = host;
+ o->jump_port = port;
+ o->proxy_command = xstrdup("none");
+ user = host = NULL; /* transferred */
+ if (orig != NULL && (cp = strrchr(orig, ',')) != NULL) {
+ o->jump_extra = xstrdup(orig);
+ o->jump_extra[cp - orig] = '\0';
}
}
ret = 0;
out:
free(orig);
+ free(sdup);
+ free(tmp_user);
+ free(tmp_host);
free(user);
free(host);
return ret;
diff --git a/readconf.h b/readconf.h
index d0eb92362..591b641cb 100644
--- a/readconf.h
+++ b/readconf.h
@@ -243,7 +243,9 @@ int process_config_line(Options *, struct passwd *, const char *,
int read_config_file(const char *, struct passwd *, const char *,
const char *, Options *, int, int *);
int parse_forward(struct Forward *, const char *, int, int);
-int parse_jump(const char *, Options *, int);
+int ssh_valid_hostname(const char *);
+int ssh_valid_ruser(const char *);
+int parse_jump(const char *, Options *, int, int);
int parse_ssh_uri(const char *, char **, char **, int *);
int default_ssh_port(void);
int option_clear_or_none(const char *);
diff --git a/ssh.c b/ssh.c
index a0371839b..e5ec18a73 100644
--- a/ssh.c
+++ b/ssh.c
@@ -626,43 +626,6 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo)
free(cinfo);
}
-static int
-valid_hostname(const char *s)
-{
- size_t i;
-
- if (*s == '-')
- return 0;
- for (i = 0; s[i] != 0; i++) {
- if (strchr("'`\"$\\;&<>|(){}", s[i]) != NULL ||
- isspace((u_char)s[i]) || iscntrl((u_char)s[i]))
- return 0;
- }
- return 1;
-}
-
-static int
-valid_ruser(const char *s)
-{
- size_t i;
-
- if (*s == '-')
- return 0;
- for (i = 0; s[i] != 0; i++) {
- if (iscntrl((u_char)s[i]))
- return 0;
- if (strchr("'`\";&<>|(){}", s[i]) != NULL)
- return 0;
- /* Disallow '-' after whitespace */
- if (isspace((u_char)s[i]) && s[i + 1] == '-')
- return 0;
- /* Disallow \ in last position */
- if (s[i] == '\\' && s[i + 1] == '\0')
- return 0;
- }
- return 1;
-}
-
/*
* Main program for the ssh client.
*/
@@ -908,9 +871,9 @@ main(int ac, char **av)
}
if (options.proxy_command != NULL)
fatal("Cannot specify -J with ProxyCommand");
- if (parse_jump(optarg, &options, 1) == -1)
+ if (parse_jump(optarg, &options, 1, 1) == -1)
+
fatal("Invalid -J argument");
- options.proxy_command = xstrdup("none");
break;
case 't':
if (options.request_tty == REQUEST_TTY_YES)
@@ -1160,10 +1123,8 @@ main(int ac, char **av)
if (!host)
usage();
- if (!valid_hostname(host))
+ if (!ssh_valid_hostname(host))
fatal("hostname contains invalid characters");
- if (options.user != NULL && !valid_ruser(options.user))
- fatal("remote username contains invalid characters");
options.host_arg = xstrdup(host);
/* Initialize the command to execute on remote host. */
@@ -1326,7 +1287,8 @@ main(int ac, char **av)
sshbin = "ssh";
/* Consistency check */
- if (options.proxy_command != NULL)
+ if (options.proxy_command != NULL &&
+ strcasecmp(options.proxy_command, "none") != 0)
fatal("inconsistent options: ProxyCommand+ProxyJump");
/* Never use FD passing for ProxyJump */
options.proxy_use_fdpass = 0;
@@ -1449,7 +1411,7 @@ main(int ac, char **av)
* Conversely, usernames from getpwnam(3) or specified as literals
* via configuration (i.e. not expanded) are not subject to validation.
*/
- if (user_on_commandline && !valid_ruser(options.user))
+ if (user_on_commandline && !ssh_valid_ruser(options.user))
fatal("remote username contains invalid characters");
/* Store it and calculate hash. */

View File

@@ -0,0 +1,51 @@
From d0b61d082621d45bf19f0aa04e9bbbc7e47e8f88 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:50:55 +0000
Subject: upstream: move username validity check for usernames specified on
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
the commandline to earlier in main(), specifically before some contexts where
a username with shell characters might be expanded by a %u directive in
ssh_config.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We continue to recommend against using untrusted input on
the SSH commandline. Mitigations like this are not 100%
guarantees of safety because we can't control every
combination of user shell and configuration where they are
used.
Reported by Florian Kohnhäuser
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35386-2.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=76685c9b09a66435cd2ad8373246adf1c53976d3
CVE: CVE-2026-35386
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
ssh.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/ssh.c b/ssh.c
index e5ec18a73..ac06bbe74 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1123,8 +1123,15 @@ main(int ac, char **av)
if (!host)
usage();
+ /*
+ * Validate commandline-specified values that end up in %tokens
+ * before they are used in config parsing.
+ */
+ if (options.user != NULL && !ssh_valid_ruser(options.user))
+ fatal("remote username contains invalid characters");
if (!ssh_valid_hostname(host))
fatal("hostname contains invalid characters");
+
options.host_arg = xstrdup(host);
/* Initialize the command to execute on remote host. */

View File

@@ -0,0 +1,25 @@
From 7a182a79ead6b39b0450059e07ffb2ce02ebd323 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:52:15 +0000
Subject: upstream: adapt to username validity check change
OpenBSD-Regress-ID: d22c66ca60f0d934a75e6ca752c4c11b9f4a5324
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35386-3.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=5aa09926fbf050d484a79717fadec8360c5c5645
CVE: CVE-2026-35386
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
regress/percent.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/regress/percent.sh b/regress/percent.sh
index 6b9c492b0..93fa4e17d 100644
--- a/regress/percent.sh
+++ b/regress/percent.sh
@@ -154,4 +154,4 @@ ${SSH} -F $OBJ/ssh_proxy -G "${FOO}@somehost" && fail "user-at expanded env"
# Literal control characters in config is acceptable
verbose $tid user control-literal
-trial user "$FOO" "$FOO"
+#trial user "$FOO" "$FOO"

View File

@@ -0,0 +1,135 @@
From 42a8064f37b82f65d3124eddafd078850132354a Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:48:13 +0000
Subject: upstream: correctly match ECDSA signature algorithms against
algorithm allowlists: HostKeyAlgorithms, PubkeyAcceptedAlgorithms and
HostbasedAcceptedAlgorithms.
Previously, if any ECDSA type (say "ecdsa-sha2-nistp521") was
present in one of these lists, then all ECDSA algorithms would
be permitted.
Reported by Christos Papakonstantinou of Cantina and Spearbit.
OpenBSD-Commit-ID: c790e2687c35989ae34a00e709be935c55b16a86
[cjwatson: Committed upstream together with apparently-unrelated changes
for CVE-2026-35414. I've split them into separate patches for clarity.]
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35387.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=fd1c7e131f331942d20f42f31e79912d570081fa
CVE: CVE-2026-35387
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
auth2-hostbased.c | 7 ++++---
auth2-pubkey.c | 7 ++++---
sshconnect2.c | 26 +++++++++++++++++---------
3 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/auth2-hostbased.c b/auth2-hostbased.c
index eb21479a0..742bda567 100644
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
@@ -96,9 +96,10 @@ userauth_hostbased(struct ssh *ssh, const char *method)
error_f("cannot decode key: %s", pkalg);
goto done;
}
- if (key->type != pktype) {
- error_f("type mismatch for decoded key "
- "(received %d, expected %d)", key->type, pktype);
+ if (key->type != pktype || (sshkey_type_plain(pktype) == KEY_ECDSA &&
+ sshkey_ecdsa_nid_from_name(pkalg) != key->ecdsa_nid)) {
+ error_f("key type mismatch for decoded key "
+ "(received %s, expected %s)", sshkey_ssh_name(key), pkalg);
goto done;
}
if (match_pattern_list(pkalg, options.hostbased_accepted_algos, 0) != 1) {
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index aa24fda05..60cf30c17 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -149,9 +149,10 @@ userauth_pubkey(struct ssh *ssh, const char *method)
error_f("cannot decode key: %s", pkalg);
goto done;
}
- if (key->type != pktype) {
- error_f("type mismatch for decoded key "
- "(received %d, expected %d)", key->type, pktype);
+ if (key->type != pktype || (sshkey_type_plain(pktype) == KEY_ECDSA &&
+ sshkey_ecdsa_nid_from_name(pkalg) != key->ecdsa_nid)) {
+ error_f("key type mismatch for decoded key "
+ "(received %s, expected %s)", sshkey_ssh_name(key), pkalg);
goto done;
}
if (auth2_key_already_used(authctxt, key)) {
diff --git a/sshconnect2.c b/sshconnect2.c
index 99ca84292..fa2625983 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -89,6 +89,7 @@ extern Options options;
static char *xxx_host;
static struct sockaddr *xxx_hostaddr;
static const struct ssh_conn_info *xxx_conn_info;
+static int key_type_allowed(struct sshkey *, const char *);
static int
verify_host_key_callback(struct sshkey *hostkey, struct ssh *ssh)
@@ -98,6 +99,10 @@ verify_host_key_callback(struct sshkey *hostkey, struct ssh *ssh)
if ((r = sshkey_check_rsa_length(hostkey,
options.required_rsa_size)) != 0)
fatal_r(r, "Bad server host key");
+ if (!key_type_allowed(hostkey, options.hostkeyalgorithms)) {
+ fatal("Server host key %s not in HostKeyAlgorithms",
+ sshkey_ssh_name(hostkey));
+ }
if (verify_host_key(xxx_host, xxx_hostaddr, hostkey,
xxx_conn_info) != 0)
fatal("Host key verification failed.");
@@ -1698,34 +1603,37 @@ load_identity_file(Identity *id)
}
static int
-key_type_allowed_by_config(struct sshkey *key)
+key_type_allowed(struct sshkey *key, const char *allowlist)
{
- if (match_pattern_list(sshkey_ssh_name(key),
- options.pubkey_accepted_algos, 0) == 1)
+ if (match_pattern_list(sshkey_ssh_name(key), allowlist, 0) == 1)
return 1;
/* RSA keys/certs might be allowed by alternate signature types */
switch (key->type) {
case KEY_RSA:
- if (match_pattern_list("rsa-sha2-512",
- options.pubkey_accepted_algos, 0) == 1)
+ if (match_pattern_list("rsa-sha2-512", allowlist, 0) == 1)
return 1;
- if (match_pattern_list("rsa-sha2-256",
- options.pubkey_accepted_algos, 0) == 1)
+ if (match_pattern_list("rsa-sha2-256", allowlist, 0) == 1)
return 1;
break;
case KEY_RSA_CERT:
if (match_pattern_list("rsa-sha2-512-cert-v01@openssh.com",
- options.pubkey_accepted_algos, 0) == 1)
+ allowlist, 0) == 1)
return 1;
if (match_pattern_list("rsa-sha2-256-cert-v01@openssh.com",
- options.pubkey_accepted_algos, 0) == 1)
+ allowlist, 0) == 1)
return 1;
break;
}
return 0;
}
+static int
+key_type_allowed_by_config(struct sshkey *key)
+{
+ return key_type_allowed(key, options.pubkey_accepted_algos);
+}
+
/* obtain a list of keys from the agent */
static int
get_agent_identities(struct ssh *ssh, int *agent_fdp,

View File

@@ -0,0 +1,39 @@
From 684fbae02b8e85e5f14e978201a3d5b68686d0fd Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:39:57 +0000
Subject: upstream: add missing askpass check when using
ControlMaster=ask/autoask and "ssh -O proxy ..."; reported by Michalis
Vasileiadis
OpenBSD-Commit-ID: 8dd7b9b96534e9a8726916b96d36bed466d3836a
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35388.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=c805b97b67c774e0bf922ffb29dfbcda9d7b5add
CVE: CVE-2026-35388
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
mux.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/mux.c b/mux.c
index e7580ac74..bda4015f4 100644
--- a/mux.c
+++ b/mux.c
@@ -1137,6 +1137,16 @@ mux_master_process_proxy(struct ssh *ssh, u_int rid,
debug_f("channel %d: proxy request", c->self);
+ if (options.control_master == SSHCTL_MASTER_ASK ||
+ options.control_master == SSHCTL_MASTER_AUTO_ASK) {
+ if (!ask_permission("Allow multiplex proxy connection?")) {
+ debug2_f("proxy refused by user");
+ reply_error(reply, MUX_S_PERMISSION_DENIED, rid,
+ "Permission denied");
+ return 0;
+ }
+ }
+
c->mux_rcb = channel_proxy_downstream;
if ((r = sshbuf_put_u32(reply, MUX_S_PROXY)) != 0 ||
(r = sshbuf_put_u32(reply, rid)) != 0)

View File

@@ -0,0 +1,78 @@
From f5daf40e6bcb932a72665c7df03ab4b5e4950e3b Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Thu, 2 Apr 2026 07:48:13 +0000
Subject: sshd(8): fix inappropriate matching of authorized_keys principals
When matching an authorized_keys principals="" option against a list of
principals in a certificate, an incorrect algorithm was used that could
allow inappropriate matching in cases where a principal name in the
certificate contains a comma character. Exploitation of the condition
requires an authorized_keys principals="" option that lists more than
one principal *and* a CA that will issue a certificate that encodes more
than one of these principal names separated by a comma (typical CAs
strongly constrain which principal names they will place in a
certificate). This condition only applies to user- trusted CA keys in
authorized_keys, the main certificate authentication path
(TrustedUserCAKeys/AuthorizedPrincipalsFile) is not affected.
Reported by Vladimir Tokarev.
OpenBSD-Commit-ID: c790e2687c35989ae34a00e709be935c55b16a86
[cjwatson: Committed upstream together with apparently-unrelated changes
for CVE-2026-35387. I've split them into separate patches for clarity.]
Upstream: https://salsa.debian.org/ssh-team/openssh/-/blob/bookworm/debian/patches/CVE-2026-35414.patch
Upstream: https://anongit.mindrot.org/openssh.git/commit/?id=fd1c7e131f331942d20f42f31e79912d570081fa
CVE: CVE-2026-35414
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
auth2-pubkeyfile.c | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/auth2-pubkeyfile.c b/auth2-pubkeyfile.c
index 0cfacac35..6b757e55b 100644
--- a/auth2-pubkeyfile.c
+++ b/auth2-pubkeyfile.c
@@ -50,6 +50,7 @@
#include "authfile.h"
#include "match.h"
#include "ssherr.h"
+#include "xmalloc.h"
int
auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts,
@@ -146,20 +147,23 @@ auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts,
static int
match_principals_option(const char *principal_list, struct sshkey_cert *cert)
{
- char *result;
+ char *list, *olist, *entry;
u_int i;
- /* XXX percent_expand() sequences for authorized_principals? */
-
- for (i = 0; i < cert->nprincipals; i++) {
- if ((result = match_list(cert->principals[i],
- principal_list, NULL)) != NULL) {
- debug3("matched principal from key options \"%.100s\"",
- result);
- free(result);
- return 1;
+ olist = list = xstrdup(principal_list);
+ for (;;) {
+ if ((entry = strsep(&list, ",")) == NULL || *entry == '\0')
+ break;
+ for (i = 0; i < cert->nprincipals; i++) {
+ if (strcmp(entry, cert->principals[i]) == 0) {
+ debug3("matched principal from key i"
+ "options \"%.100s\"", entry);
+ free(olist);
+ return 1;
+ }
}
}
+ free(olist);
return 0;
}

View File

@@ -15,6 +15,23 @@ OPENSSH_LICENSE_FILES = LICENCE
# 0001-fix-logic-error-in-disableforwarding-option.patch
OPENSSH_IGNORE_CVES += CVE-2025-32728
# 0002-CVE-2025-61984-tests.patch
# 0003-CVE-2025-61984.patch
OPENSSH_IGNORE_CVES += CVE-2025-61984
# 0004-CVE-2025-61985.patch
OPENSSH_IGNORE_CVES += CVE-2025-61985
# 0005-CVE-2026-35385.patch
OPENSSH_IGNORE_CVES += CVE-2026-35385
# 0006-CVE-2026-35386-1.patch
# 0007-CVE-2026-35386-2.patch
# 0008-CVE-2026-35386-3.patch
OPENSSH_IGNORE_CVES += CVE-2026-35386
# 0009-CVE-2026-35387.patch
OPENSSH_IGNORE_CVES += CVE-2026-35387
# 0010-CVE-2026-35388.patch
OPENSSH_IGNORE_CVES += CVE-2026-35388
# 0011-CVE-2026-35414.patch
OPENSSH_IGNORE_CVES += CVE-2026-35414
OPENSSH_CONF_ENV = \
LD="$(TARGET_CC)" \