--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:openssh
User: [email protected]
Usertags: pu
[ Reason ]
Firstly, OpenSSH 10.3 included several minor security updates. The
security team has marked them no-dsa, but I'd like to fix them in a
stable update.
Secondly, the IPQoS situation in Debian's OpenSSH packages has been
unsatisfactory for some time. We've previously been carrying a patch
that reverted changes made in OpenSSH 7.8 due to regressions in iptables
and VMware (the latter of which was fixed in 2019). In OpenSSH 10.1,
upstream reworked their QoS support to use EF (Expedited Forwarding) for
interactive traffic and the OS default for non-interactive traffic,
which is especially useful for differentiated treatment over wireless
networks; they also now adapt the QoS value when a non-interactive
channel is open even if the session started interactive, which produces
much better results in various situations where sessions move large
amounts of data despite initially appearing to be interactive.
Upstream says that they've broadly found these to be significant
improvements to traffic management of SSH flows; so far they've only had
two reports of regressions, which both turned out to be ISP-level
problems. Since I've had no reports of problems after six months of
these changes having been in forky, I'd far prefer to use upstream's
traffic management settings even in trixie, and to drop the previous
rather hacky Debian-specific patch.
Using EF for interactive traffic should no longer run into
https://bugs.debian.org/923880 in iptables, as explained in more detail
in my most recent message to that bug.
[ Impact ]
Several security issues that are minor for most people although major in
some situations (in particular, CVE-2026-35414 can be an authentication
bypass in some configurations).
For the IPQoS changes, I have less in the way of concrete data, although
I've checked my proposed changes with upstream. I think the typical
impact of not approving these changes will be deprioritized interactive
traffic in some network environments, particularly wireless or otherwise
constrained networks.
[ Tests ]
The OpenSSH packaging runs an extensive set of automated tests via
autopkgtest, some of which are adjusted for some of the security
updates.
[ Risks ]
There are two intentional incompatibilities introduced by the security
updates (descriptions here are from the upstream release notes):
* sshd(8): prior to this release, a certificate that had an empty
principals section would be treated as matching any principal (i.e. as a
wildcard) when used via authorized_keys principals="" option. This was
intentional, but created a surprising and potentially risky situation if
a CA accidentally issued a certificate with an empty principals section:
instead of being useless as one might expect, it could be used to
authenticate as any user who trusted the CA via authorized_keys. [Note
that this condition did not apply to CAs trusted via the sshd_config(5)
TrustedUserCAKeys option.]
This release treats an empty principals section as never matching any
principal, and also fixes interpretation of wildcard characters in
certificate principals. Now they are consistently implemented for host
certificates and not supported for user certificates.
* ssh(1): the -J and equivalent -oProxyJump="..." options now validate
user and host names for ProxyJump/-J options passed via the command-line
(no such validation is performed for this option in configuration
files). This prevents shell injection in situations where these were
directly exposed to adversarial input, which would have been a terrible
idea to begin with.
For the IPQoS changes, I'll again quote the upstream release notes:
* ssh(1), sshd(8): major changes to handling of DSCP marking/IPQoS
In both client and server the default DSCP (a.k.a IPQoS) values were
revised and the way these values are used during runtime has changed.
Interactive traffic is now assigned to the EF (Expedited Forwarding)
class by default. This provides more appropriate packet prioritisation
information for the intermediate network, such as wireless media (cf.
RFC 8325). Non-interactive traffic will now use the operating system
default DSCP marking. Both the interactive and non-interactive DSCP
values may be overridden via the IPQoS keyword, described in
ssh_config(5) and sshd_config(5).
The appropriate DSCP marking is now automatically selected and updated
as needed over the course of a connection's lifetime. ssh(1) and
sshd(8) will switch between the interactive and non-interactive IPQoS
values depending on the type of SSH channels open at the time. For
example, if an sftp session is using the connection alongside a shell
session, then the non- interactive value will be used for the duration
of the sftp. A connection which contains only interactive sessions is
marked EF.
* ssh(1), sshd(8): deprecate support for IPv4 type-of-service (ToS)
keywords in the IPQoS configuration directive.
Type of Service (ToS) was deprecated in the late nineties and replaced
with the Differentiated Services architecture, which has significant
advantages for operators because it offers more granularity.
OpenSSH switched its default IPQoS from ToS to DSCP values in 2018
(openssh-7.7).
IPQoS configurations with 'lowdelay', 'reliability', or 'throughput'
will be ignored and will instead use the system default QoS settings.
Additionally, a debug message will be logged about the deprecation with
a suggestion to use DSCP QoS instead.
I think these are all worthwhile and will generally be improvements, but
I'm happy to discuss them if needed. In particular, it would probably
be possible to skip the deprecation of IPv4 ToS keywords in this update
(although I do think those keywords are best regarded as a subtle trap).
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
Thanks,
--
Colin Watson (he/him) [[email protected]]
diff -Nru openssh-10.0p1/debian/.git-dpm openssh-10.0p1/debian/.git-dpm
--- openssh-10.0p1/debian/.git-dpm 2026-04-05 00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/.git-dpm 2026-05-01 17:26:08.000000000 +0100
@@ -1,6 +1,6 @@
# see git-dpm(1) from git-dpm package
-947d15f4b44cf7d4ce337c82ed7e1a167a4f4dc2
-947d15f4b44cf7d4ce337c82ed7e1a167a4f4dc2
+4207d8a7a4060cad77ec1b78ff08f3e0546c4fbd
+4207d8a7a4060cad77ec1b78ff08f3e0546c4fbd
860fa104f07024318a40065f07708daa5753f55d
860fa104f07024318a40065f07708daa5753f55d
openssh_10.0p1.orig.tar.gz
diff -Nru openssh-10.0p1/debian/changelog openssh-10.0p1/debian/changelog
--- openssh-10.0p1/debian/changelog 2026-04-05 00:29:20.000000000 +0100
+++ openssh-10.0p1/debian/changelog 2026-05-01 17:26:08.000000000 +0100
@@ -1,3 +1,64 @@
+openssh (1:10.0p1-7+deb13u3) trixie; urgency=medium
+
+ * Backport minor security fixes from 10.3p1:
+ - ssh(1): the -J and equivalent -oProxyJump="..." options now validate
+ user and host names for ProxyJump/-J options passed via the
+ command-line (no such validation is performed for this option in
+ configuration files). This prevents shell injection in situations
+ where these were directly exposed to adversarial input, which would
+ have been a terrible idea to begin with.
+ - CVE-2026-35386: ssh(1): validation of shell metacharacters in user
+ names supplied on the command-line was performed too late to prevent
+ some situations where they could be expanded from %-tokens in
+ ssh_config. For certain configurations, such as those that use a "%u"
+ token in a "Match exec" block, an attacker who can control the user
+ name passed to ssh(1) could potentially execute arbitrary shell
+ commands. Reported by Florian Kohnhäuser (closes: #1132573).
+ We continue to recommend against directly exposing ssh(1) and other
+ tools' command-lines to untrusted input. Mitigations such as this can
+ not be absolute given the variety of shells and user configurations in
+ use.
+ - CVE-2026-35414: sshd(8): 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 (closes: #1132576).
+ - CVE-2026-35385: scp(1): when downloading files as root in legacy (-O)
+ mode and without the -p (preserve modes) flag set, scp did not clear
+ setuid/setgid bits from downloaded files as one might typically
+ expect. This bug dates back to the original Berkeley rcp program.
+ Reported by Christos Papakonstantinou of Cantina and Spearbit (closes:
+ #1132572).
+ - CVE-2026-35387: sshd(8): fix incomplete application of
+ PubkeyAcceptedAlgorithms and HostbasedAcceptedAlgorithms with regard
+ to ECDSA keys. Previously if one of these directives contains any
+ ECDSA algorithm name (say "ecdsa-sha2-nistp384"), then any other ECDSA
+ algorithm would be accepted in its place regardless of whether it was
+ listed or not. Reported by Christos Papakonstantinou of Cantina and
+ Spearbit (closes: #1132574).
+ - CVE-2026-35388: ssh(1): connection multiplexing confirmation
+ (requested using "ControlMaster ask/autoask") was not being tested for
+ proxy mode multiplexing sessions (i.e. "ssh -O proxy ..."). Reported
+ by Michalis Vasileiadis (closes: #1132575).
+ * Cherry-pick IPQoS handling updates from upstream:
+ - Set default IPQoS for interactive sessions to Expedited Forwarding
+ (EF).
+ - Deprecate support for IPv4 type-of-service (TOS) IPQoS keywords.
+ - Make ssh(1) and sshd(8) set IP QoS (aka IP_TOS, IPV6_TCLASS)
+ continually at runtime based on what sessions/channels are open.
+ - Correctly set extended type for client-side channels. Fixes
+ interactive vs bulk IPQoS for client->server traffic.
+
+ -- Colin Watson <[email protected]> Fri, 01 May 2026 17:26:08 +0100
+
openssh (1:10.0p1-7+deb13u2) trixie-security; urgency=medium
* CVE-2026-3497: Fix incorrect GSS-API error handling; Replace incorrect
diff -Nru openssh-10.0p1/debian/patches/CVE-2025-61984-tests.patch
openssh-10.0p1/debian/patches/CVE-2025-61984-tests.patch
--- openssh-10.0p1/debian/patches/CVE-2025-61984-tests.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2025-61984-tests.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 4a8b438b5a7cd0534dbfa11e953935ae24debbc6 Mon Sep 17 00:00:00 2001
+From 8644b923d0b735dafd16d80deb3576d7055d8cf2 Mon Sep 17 00:00:00 2001
From: "[email protected]" <[email protected]>
Date: Thu, 4 Sep 2025 03:04:44 +0000
Subject: upstream: repair test after changes to percent expansion of usernames
diff -Nru openssh-10.0p1/debian/patches/CVE-2025-61984.patch
openssh-10.0p1/debian/patches/CVE-2025-61984.patch
--- openssh-10.0p1/debian/patches/CVE-2025-61984.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2025-61984.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 82a6200c6affd9a90b3fe8e2fdea93b839319aea Mon Sep 17 00:00:00 2001
+From 7f30210200b3f7f4cf1eddfcee49a435f8b68419 Mon Sep 17 00:00:00 2001
From: "[email protected]" <[email protected]>
Date: Thu, 4 Sep 2025 00:29:09 +0000
Subject: upstream: Improve rules for %-expansion of username.
diff -Nru openssh-10.0p1/debian/patches/CVE-2025-61985.patch
openssh-10.0p1/debian/patches/CVE-2025-61985.patch
--- openssh-10.0p1/debian/patches/CVE-2025-61985.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2025-61985.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 51b9b26c9f76b2594ca93ce1ac49aa10931d098a Mon Sep 17 00:00:00 2001
+From db9b2c27baa9b28764685c1383ebda333ecc76ba Mon Sep 17 00:00:00 2001
From: "[email protected]" <[email protected]>
Date: Thu, 4 Sep 2025 00:30:06 +0000
Subject: upstream: don't allow \0 characters in url-encoded strings.
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35385.patch
openssh-10.0p1/debian/patches/CVE-2026-35385.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35385.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35385.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,39 @@
+From 8be19efcbe7ca5e2eadde1a19dcb4763b380c8e5 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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.
+
+OpenBSD-Commit-ID: 49e902fca8dd933a92a9b547ab31f63e86729fa1
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=487e8ac146f7d6616f65c125d5edb210519b833a
+Bug-Debian: https://bugs.debian.org/1132572
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35385.patch
+---
+ scp.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/scp.c b/scp.c
+index ec2040a6c..2199a7376 100644
+--- a/scp.c
++++ b/scp.c
+@@ -1693,8 +1693,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);
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35386-1.patch
openssh-10.0p1/debian/patches/CVE-2026-35386-1.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35386-1.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35386-1.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,318 @@
+From df454d9e693b6af926e7a61929c14adcbecb96b7 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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@
+
+OpenBSD-Commit-ID: f05ad8a1eb5f6735f9a935a71a90580226759263
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=0a0ef4515361143cad21afa072319823854c1cf6
+Bug-Debian: https://bugs.debian.org/1132573
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35386-1.patch
+---
+ 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 fc625a00c..3bb7ac249 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -1585,9 +1585,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.",
+@@ -1608,7 +1605,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;
+@@ -3444,65 +3441,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 368523dd7..58a44c2e8 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -250,7 +250,9 @@ int process_config_line(Options *, struct passwd
*, const char *,
+ int read_config_file(const char *, struct passwd *, const char *,
+ 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 9a8267869..a2fe324f5 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -631,43 +631,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.
+ */
+@@ -918,9 +881,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)
+@@ -1170,7 +1133,7 @@ main(int ac, char **av)
+ if (!host)
+ usage();
+
+- if (!valid_hostname(host))
++ if (!ssh_valid_hostname(host))
+ fatal("hostname contains invalid characters");
+ options.host_arg = xstrdup(host);
+
+@@ -1341,7 +1304,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;
+@@ -1481,7 +1445,7 @@ main(int ac, char **av)
+ * via configuration (i.e. not expanded) are not subject to validation.
+ */
+ if ((user_on_commandline || user_expanded) &&
+- !valid_ruser(options.user))
++ !ssh_valid_ruser(options.user))
+ fatal("remote username contains invalid characters");
+
+ /* Now User is expanded, store it and calculate hash. */
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35386-2.patch
openssh-10.0p1/debian/patches/CVE-2026-35386-2.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35386-2.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35386-2.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,54 @@
+From c03c0da2f6975aaab06ea98025213c8ea2fb5f8d Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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
+
+OpenBSD-Commit-ID: 25ef72223f5ccf1c38d307ae77c23c03f59acc55
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=76685c9b09a66435cd2ad8373246adf1c53976d3
+Bug-Debian: https://bugs.debian.org/1132573
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35386-2.patch
+---
+ ssh.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/ssh.c b/ssh.c
+index a2fe324f5..e668fb771 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -1133,8 +1133,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. */
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35386-3.patch
openssh-10.0p1/debian/patches/CVE-2026-35386-3.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35386-3.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35386-3.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,38 @@
+From b2c2f35eb41467aad55636283a7f849260312a0d Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+Date: Thu, 2 Apr 2026 07:52:15 +0000
+Subject: upstream: adapt to username validity check change
+
+OpenBSD-Regress-ID: d22c66ca60f0d934a75e6ca752c4c11b9f4a5324
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=5aa09926fbf050d484a79717fadec8360c5c5645
+Bug-Debian: https://bugs.debian.org/1132573
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35386-3.patch
+---
+ regress/percent.sh | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/regress/percent.sh b/regress/percent.sh
+index c607c8d23..bf4e1f2e5 100644
+--- a/regress/percent.sh
++++ b/regress/percent.sh
+@@ -140,7 +140,7 @@ done
+ FOO=bar
+ export FOO
+ for i in controlpath identityagent forwardagent localforward remoteforward \
+- user setenv userknownhostsfile; do
++ setenv userknownhostsfile; do
+ verbose $tid $i dollar
+ trial $i '${FOO}' $FOO
+ done
+@@ -175,7 +175,7 @@ ${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"
+
+ # Control characters expanded from config aren't.
+ ${SSH} -F $OBJ/ssh_proxy -G '-oUser=${FOO}' somehost && \
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35387.patch
openssh-10.0p1/debian/patches/CVE-2026-35387.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35387.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35387.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,136 @@
+From 1f1029c07d64f668eccadb51b8e93ea250e9c62a Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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.]
+
+Origin: backport,
https://anongit.mindrot.org/openssh.git/commit/?id=fd1c7e131f331942d20f42f31e79912d570081fa
+Bug-Debian: https://bugs.debian.org/1132574
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35387.patch
+---
+ 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
+@@ -154,9 +154,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.");
+@@ -1738,34 +1743,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("[email protected]",
+- options.pubkey_accepted_algos, 0) == 1)
++ allowlist, 0) == 1)
+ return 1;
+ if (match_pattern_list("[email protected]",
+- 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,
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35388.patch
openssh-10.0p1/debian/patches/CVE-2026-35388.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35388.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35388.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,40 @@
+From 15b6d7bb9b5e81f4de1f7ff824066ff3f910e6c5 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=c805b97b67c774e0bf922ffb29dfbcda9d7b5add
+Bug-Debian: https://bugs.debian.org/1132575
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35388.patch
+---
+ mux.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/mux.c b/mux.c
+index 415024f74..b48e69a12 100644
+--- a/mux.c
++++ b/mux.c
+@@ -1136,6 +1136,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)
diff -Nru openssh-10.0p1/debian/patches/CVE-2026-35414.patch
openssh-10.0p1/debian/patches/CVE-2026-35414.patch
--- openssh-10.0p1/debian/patches/CVE-2026-35414.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/CVE-2026-35414.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,79 @@
+From 8e9de40e4bec5c7ccf48c72f7b51d52aeb0d8df9 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+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.]
+
+Origin: backport,
https://anongit.mindrot.org/openssh.git/commit/?id=fd1c7e131f331942d20f42f31e79912d570081fa
+Bug-Debian: https://bugs.debian.org/1132576
+Last-Update: 2026-05-01
+
+Patch-Name: CVE-2026-35414.patch
+---
+ auth2-pubkeyfile.c | 24 ++++++++++++++----------
+ 1 file changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/auth2-pubkeyfile.c b/auth2-pubkeyfile.c
+index 31e7481fb..827b71ed2 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;
+ }
+
diff -Nru openssh-10.0p1/debian/patches/configure-cache-vars.patch
openssh-10.0p1/debian/patches/configure-cache-vars.patch
--- openssh-10.0p1/debian/patches/configure-cache-vars.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/configure-cache-vars.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 632c556fc44085e0cf62c92fbea312bc2ff01700 Mon Sep 17 00:00:00 2001
+From ab502cbb63f4665ed3963c9b2015468eeb3b0274 Mon Sep 17 00:00:00 2001
From: Colin Watson <[email protected]>
Date: Wed, 3 Apr 2024 11:52:04 +0100
Subject: Add Autoconf cache variables for OSSH_CHECK_*FLAG_*
diff -Nru openssh-10.0p1/debian/patches/fix-max-startups-tracking.patch
openssh-10.0p1/debian/patches/fix-max-startups-tracking.patch
--- openssh-10.0p1/debian/patches/fix-max-startups-tracking.patch
2026-04-05 00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/fix-max-startups-tracking.patch
2026-05-01 17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 947d15f4b44cf7d4ce337c82ed7e1a167a4f4dc2 Mon Sep 17 00:00:00 2001
+From 26128e0a946eff65c311f8c00f2449e1efb7fdff Mon Sep 17 00:00:00 2001
From: "[email protected]" <[email protected]>
Date: Fri, 4 Jul 2025 09:51:01 +0000
Subject: upstream: Fix mistracking of MaxStartups process exits in some
diff -Nru openssh-10.0p1/debian/patches/ipqos-deprecate-tos-keywords.patch
openssh-10.0p1/debian/patches/ipqos-deprecate-tos-keywords.patch
--- openssh-10.0p1/debian/patches/ipqos-deprecate-tos-keywords.patch
1970-01-01 01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/ipqos-deprecate-tos-keywords.patch
2026-05-01 17:26:08.000000000 +0100
@@ -0,0 +1,175 @@
+From e93be079c0c564cf17acc18a7a6352c0f75e2bf0 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+Date: Thu, 31 Jul 2025 11:23:39 +0000
+Subject: upstream: Deprecate support for IPv4 type-of-service (TOS) IPQoS
+
+keywords
+
+Type of Service (ToS) was deprecated in the late nineties and replaced
+with the Differentiated Services architecture. Diffserv has significant
+advantages for operators because this mechanism offers more granularity.
+
+OpenSSH switched its default IPQoS from ToS to DSCP values in 2018.
+
+IPQoS configurations with 'lowdelay', 'reliability', or 'throughput' will be
+ignored and instead the system default QoS settings apply. Additionally, a
+debug message is logged about the deprecation with a suggestion to use DSCP.
+
+with/OK deraadt@ sthen@ djm@
+
+OpenBSD-Commit-ID: 40c8c0c5cb20151a348728703536af2ec1c754ba
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=ec3465f59c651405e395092f3ad606f8992328d8
+Last-Update: 2026-05-03
+
+Patch-Name: ipqos-deprecate-tos-keywords.patch
+---
+ misc.c | 6 +++---
+ readconf.c | 12 ++++++++++++
+ readconf.h | 4 ++--
+ servconf.c | 12 ++++++++++++
+ ssh_config.5 | 7 +++----
+ sshd_config.5 | 7 +++----
+ 6 files changed, 35 insertions(+), 13 deletions(-)
+
+diff --git a/misc.c b/misc.c
+index 3b02281bb..3ff464573 100644
+--- a/misc.c
++++ b/misc.c
+@@ -1935,9 +1935,9 @@ static const struct {
+ { "cs7", IPTOS_DSCP_CS7 },
+ { "ef", IPTOS_DSCP_EF },
+ { "le", IPTOS_DSCP_LE },
+- { "lowdelay", IPTOS_LOWDELAY },
+- { "throughput", IPTOS_THROUGHPUT },
+- { "reliability", IPTOS_RELIABILITY },
++ { "lowdelay", INT_MIN }, /* deprecated */
++ { "throughput", INT_MIN }, /* deprecated */
++ { "reliability", INT_MIN }, /* deprecated */
+ { NULL, -1 }
+ };
+
+diff --git a/readconf.c b/readconf.c
+index 796c16f23..2d1551420 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -2212,6 +2212,12 @@ parse_pubkey_algos:
+ filename, linenum, arg);
+ goto out;
+ }
++ if (value == INT_MIN) {
++ debug("%s line %d: Deprecated IPQoS value \"%s\" "
++ "ignored - using system default instead. Consider"
++ " using DSCP values.", filename, linenum, arg);
++ value = INT_MAX;
++ }
+ arg = argv_next(&ac, &av);
+ if (arg == NULL)
+ value2 = value;
+@@ -2220,6 +2226,12 @@ parse_pubkey_algos:
+ filename, linenum, arg);
+ goto out;
+ }
++ if (value2 == INT_MIN) {
++ debug("%s line %d: Deprecated IPQoS value \"%s\" "
++ "ignored - using system default instead. Consider"
++ " using DSCP values.", filename, linenum, arg);
++ value2 = INT_MAX;
++ }
+ if (*activep && options->ip_qos_interactive == -1) {
+ options->ip_qos_interactive = value;
+ options->ip_qos_bulk = value2;
+diff --git a/readconf.h b/readconf.h
+index 58a44c2e8..c76960535 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -55,8 +55,8 @@ typedef struct {
+ int strict_host_key_checking; /* Strict host key checking. */
+ int compression; /* Compress packets in both directions. */
+ int tcp_keep_alive; /* Set SO_KEEPALIVE. */
+- int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */
+- int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */
++ int ip_qos_interactive; /* DSCP value for interactive */
++ int ip_qos_bulk; /* DSCP value for bulk traffic */
+ SyslogFacility log_facility; /* Facility for system logging. */
+ LogLevel log_level; /* Level for logging. */
+ u_int num_log_verbose; /* Verbose log overrides */
+diff --git a/servconf.c b/servconf.c
+index 490ef074d..64caf0bab 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -2567,12 +2567,24 @@ process_server_config_line_depth(ServerOptions
*options, char *line,
+ if ((value = parse_ipqos(arg)) == -1)
+ fatal("%s line %d: Bad %s value: %s",
+ filename, linenum, keyword, arg);
++ if (value == INT_MIN) {
++ debug("%s line %d: Deprecated IPQoS value \"%s\" "
++ "ignored - using system default instead. Consider"
++ " using DSCP values.", filename, linenum, arg);
++ value = INT_MAX;
++ }
+ arg = argv_next(&ac, &av);
+ if (arg == NULL)
+ value2 = value;
+ else if ((value2 = parse_ipqos(arg)) == -1)
+ fatal("%s line %d: Bad %s value: %s",
+ filename, linenum, keyword, arg);
++ if (value2 == INT_MIN) {
++ debug("%s line %d: Deprecated IPQoS value \"%s\" "
++ "ignored - using system default instead. Consider"
++ " using DSCP values.", filename, linenum, arg);
++ value2 = INT_MAX;
++ }
+ if (*activep) {
+ options->ip_qos_interactive = value;
+ options->ip_qos_bulk = value2;
+diff --git a/ssh_config.5 b/ssh_config.5
+index 75c85ce48..2ee104bf8 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1332,7 +1332,9 @@ or
+ block
+ to perform conditional inclusion.
+ .It Cm IPQoS
+-Specifies the IPv4 type-of-service or DSCP class for connections.
++Specifies the
++.Em Differentiated Services Field Codepoint Pq DSCP
++value for connections.
+ Accepted values are
+ .Cm af11 ,
+ .Cm af12 ,
+@@ -1356,9 +1358,6 @@ Accepted values are
+ .Cm cs7 ,
+ .Cm ef ,
+ .Cm le ,
+-.Cm lowdelay ,
+-.Cm throughput ,
+-.Cm reliability ,
+ a numeric value, or
+ .Cm none
+ to use the operating system default.
+diff --git a/sshd_config.5 b/sshd_config.5
+index a1afb4f89..8f51cd886 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -987,7 +987,9 @@ directive may appear inside a
+ block
+ to perform conditional inclusion.
+ .It Cm IPQoS
+-Specifies the IPv4 type-of-service or DSCP class for the connection.
++Specifies the
++.Em Differentiated Services Field Codepoint Pq DSCP
++value for the connection.
+ Accepted values are
+ .Cm af11 ,
+ .Cm af12 ,
+@@ -1011,9 +1013,6 @@ Accepted values are
+ .Cm cs7 ,
+ .Cm ef ,
+ .Cm le ,
+-.Cm lowdelay ,
+-.Cm throughput ,
+-.Cm reliability ,
+ a numeric value, or
+ .Cm none
+ to use the operating system default.
diff -Nru openssh-10.0p1/debian/patches/ipqos-interactive-ef.patch
openssh-10.0p1/debian/patches/ipqos-interactive-ef.patch
--- openssh-10.0p1/debian/patches/ipqos-interactive-ef.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/ipqos-interactive-ef.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,86 @@
+From 2a343893126e8fb4a6504c94a012e3a6e1dc4ac0 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+Date: Thu, 31 Jul 2025 09:38:41 +0000
+Subject: upstream: Set default IPQoS for interactive sessions to Expedited
+
+Forwarding (EF)
+
+Marking interactive session data with DSCP value EF (RFC3246, RFC3247)
+helps inform the network on relative priority compared to other traffic.
+This is especially useful for differentiated treatment over wireless media.
+
+Following the reconciled IETF Diffserv to IEEE 802.11 mappings (RFC 8325),
+traffic marked with DSCP value EF maps to User Priority 6 in QoS Control,
+in turn mapping to the high priority WMM AC_VO access category.
+
+OK djm@
+
+OpenBSD-Commit-ID: aadda7b9da794d70d7c6b381a861a0610afce1b3
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=65909fa114e7dd7511800db2b7bacb8774afe887
+Last-Update: 2026-05-03
+
+Patch-Name: ipqos-interactive-ef.patch
+---
+ readconf.c | 2 +-
+ servconf.c | 2 +-
+ ssh_config.5 | 4 ++--
+ sshd_config.5 | 4 ++--
+ 4 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/readconf.c b/readconf.c
+index 3bb7ac249..796c16f23 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -3009,7 +3009,7 @@ fill_default_options(Options * options)
+ if (options->visual_host_key == -1)
+ options->visual_host_key = 0;
+ if (options->ip_qos_interactive == -1)
+- options->ip_qos_interactive = IPTOS_DSCP_AF21;
++ options->ip_qos_interactive = IPTOS_DSCP_EF;
+ if (options->ip_qos_bulk == -1)
+ options->ip_qos_bulk = IPTOS_DSCP_CS1;
+ if (options->request_tty == -1)
+diff --git a/servconf.c b/servconf.c
+index 4891a43d6..490ef074d 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -485,7 +485,7 @@ fill_default_server_options(ServerOptions *options)
+ if (options->permit_tun == -1)
+ options->permit_tun = SSH_TUNMODE_NO;
+ if (options->ip_qos_interactive == -1)
+- options->ip_qos_interactive = IPTOS_DSCP_AF21;
++ options->ip_qos_interactive = IPTOS_DSCP_EF;
+ if (options->ip_qos_bulk == -1)
+ options->ip_qos_bulk = IPTOS_DSCP_CS1;
+ if (options->version_addendum == NULL)
+diff --git a/ssh_config.5 b/ssh_config.5
+index d8452237d..75c85ce48 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1367,8 +1367,8 @@ If one argument is specified, it is used as the packet
class unconditionally.
+ If two values are specified, the first is automatically selected for
+ interactive sessions and the second for non-interactive sessions.
+ The default is
+-.Cm af21
+-(Low-Latency Data)
++.Cm ef
++(Expedited Forwarding)
+ for interactive sessions and
+ .Cm cs1
+ (Lower Effort)
+diff --git a/sshd_config.5 b/sshd_config.5
+index a5594102f..a1afb4f89 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1022,8 +1022,8 @@ If one argument is specified, it is used as the packet
class unconditionally.
+ If two values are specified, the first is automatically selected for
+ interactive sessions and the second for non-interactive sessions.
+ The default is
+-.Cm af21
+-(Low-Latency Data)
++.Cm ef
++(Expedited Forwarding)
+ for interactive sessions and
+ .Cm cs1
+ (Lower Effort)
diff -Nru openssh-10.0p1/debian/patches/ipqos-set-at-runtime.patch
openssh-10.0p1/debian/patches/ipqos-set-at-runtime.patch
--- openssh-10.0p1/debian/patches/ipqos-set-at-runtime.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/ipqos-set-at-runtime.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,595 @@
+From 62e161b58f41afdf8d3bdca5ec2a84c348775169 Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+Date: Mon, 18 Aug 2025 03:43:01 +0000
+Subject: upstream: Make ssh(1) and sshd(8) set IP QoS (aka IP_TOS,
+ IPV6_TCLASS)
+
+continually at runtime based on what sessions/channels are open.
+
+Previously, ssh(1) and sshd(8) would pick a QoS value when they
+were started and use it for the whole connection. This could
+produce suboptimal choices for the QoS value, e.g. for multiplexed
+sessions that started interactive but picked up a sftp client,
+or sessions that moved large amounts of data via port forwarding.
+
+Now the QoS value will change to the non-interactive IPQoS whenever
+a "non-interactive" channel is open; basically any channel that lacks
+a tty other than agent forwarding.
+
+This is important now that the default interactive IPQoS is EF
+(Expedited Forwarding), as many networks are configured to allow
+only relatively small amounts of traffic of this class and they will
+aggressively deprioritise the entire connection if this is exceeded.
+
+NB. because ssh(1) and sshd(8) now change IP_TOS/IPV6_TCLASS
+continually via setsockopt(), this commit requires a recent pledge(2)
+change that landed recently in the OpenBSD kernel. Please ensure
+you have updated to a kernel from within the last two weeks before
+updating OpenSSH.
+
+with job@ deraadt@
+
+OpenBSD-Commit-ID: 325fc41717eecdf5e4b534bfa8d66817425b840f
+
+Origin: backport,
https://anongit.mindrot.org/openssh.git/commit/?id=289239046b2c4b0076c14394ae9703a879e78706
+Last-Update: 2026-05-03
+
+Patch-Name: ipqos-set-at-runtime.patch
+---
+ channels.c | 44 ++++++++++++++++++++++++++++---
+ channels.h | 7 +++++
+ clientloop.c | 11 +++++---
+ misc.c | 4 +++
+ mux.c | 2 ++
+ packet.c | 71 ++++++++++++++++++++++++++++++++++----------------
+ packet.h | 3 ++-
+ serverloop.c | 7 +++++
+ session.c | 5 ----
+ ssh.c | 25 +++++-------------
+ sshd-auth.c | 2 ++
+ sshd-session.c | 2 ++
+ 12 files changed, 130 insertions(+), 53 deletions(-)
+
+diff --git a/channels.c b/channels.c
+index bfe2e3b2d..1a8f8ad4a 100644
+--- a/channels.c
++++ b/channels.c
+@@ -212,6 +212,10 @@ struct ssh_channels {
+ /* Global timeout for all OPEN channels */
+ int global_deadline;
+ time_t lastused;
++ /* pattern-lists used to classify channels as bulk */
++ char *bulk_classifier_tty, *bulk_classifier_notty;
++ /* Number of active bulk channels (set by channel_handler) */
++ u_int nbulk;
+ };
+
+ /* helper */
+@@ -239,6 +243,8 @@ channel_init_channels(struct ssh *ssh)
+ sc->channels_alloc = 10;
+ sc->channels = xcalloc(sc->channels_alloc, sizeof(*sc->channels));
+ sc->IPv4or6 = AF_UNSPEC;
++ sc->bulk_classifier_tty = xstrdup(CHANNEL_BULK_TTY);
++ sc->bulk_classifier_notty = xstrdup(CHANNEL_BULK_NOTTY);
+ channel_handler_init(sc);
+
+ ssh->chanctxt = sc;
+@@ -357,6 +363,17 @@ lookup_timeout(struct ssh *ssh, const char *type)
+ return 0;
+ }
+
++static void
++channel_classify(struct ssh *ssh, Channel *c)
++{
++ struct ssh_channels *sc = ssh->chanctxt;
++ const char *type = c->xctype == NULL ? c->ctype : c->xctype;
++ const char *classifier = c->isatty ?
++ sc->bulk_classifier_tty : sc->bulk_classifier_notty;
++
++ c->bulk = type != NULL && match_pattern_list(type, classifier, 0) == 1;
++}
++
+ /*
+ * Sets "extended type" of a channel; used by session layer to add additional
+ * information about channel types (e.g. shell, login, subsystem) that can
then
+@@ -375,6 +392,7 @@ channel_set_xtype(struct ssh *ssh, int id, const char
*xctype)
+ c->xctype = xstrdup(xctype);
+ /* Type has changed, so look up inactivity deadline again */
+ c->inactive_deadline = lookup_timeout(ssh, c->xctype);
++ channel_classify(ssh, c);
+ debug2_f("labeled channel %d as %s (inactive timeout %u)", id, xctype,
+ c->inactive_deadline);
+ }
+@@ -411,6 +429,13 @@ channel_get_expiry(struct ssh *ssh, Channel *c)
+ return expiry;
+ }
+
++/* Returns non-zero if there is an open, non-interactive channel */
++int
++channel_has_bulk(struct ssh *ssh)
++{
++ return ssh->chanctxt != NULL && ssh->chanctxt->nbulk != 0;
++}
++
+ /*
+ * Register filedescriptors for a channel, used when allocating a channel or
+ * when the channel consumer/producer is ready, e.g. shell exec'd
+@@ -478,6 +503,7 @@ channel_register_fds(struct ssh *ssh, Channel *c, int rfd,
int wfd, int efd,
+ }
+ /* channel might be entering a larval state, so reset global timeout */
+ channel_set_used_time(ssh, NULL);
++ channel_classify(ssh, c);
+ }
+
+ /*
+@@ -537,11 +563,19 @@ channel_new(struct ssh *ssh, char *ctype, int type, int
rfd, int wfd, int efd,
+ c->delayed = 1; /* prevent call to channel_post handler */
+ c->inactive_deadline = lookup_timeout(ssh, c->ctype);
+ TAILQ_INIT(&c->status_confirms);
++ channel_classify(ssh, c);
+ debug("channel %d: new %s [%s] (inactive timeout: %u)",
+ found, c->ctype, remote_name, c->inactive_deadline);
+ return c;
+ }
+
++void
++channel_set_tty(struct ssh *ssh, Channel *c)
++{
++ c->isatty = 1;
++ channel_classify(ssh, c);
++}
++
+ int
+ channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
+ {
+@@ -1019,7 +1053,7 @@ channel_format_status(const Channel *c)
+ char *ret = NULL;
+
+ xasprintf(&ret, "t%d [%s] %s%u %s%u i%u/%zu o%u/%zu e[%s]/%zu "
+- "fd %d/%d/%d sock %d cc %d %s%u io 0x%02x/0x%02x",
++ "fd %d/%d/%d sock %d cc %d %s%u io 0x%02x/0x%02x %s%s",
+ c->type, c->xctype != NULL ? c->xctype : c->ctype,
+ c->have_remote_id ? "r" : "nr", c->remote_id,
+ c->mux_ctx != NULL ? "m" : "nm", c->mux_downstream_id,
+@@ -1028,7 +1062,8 @@ channel_format_status(const Channel *c)
+ channel_format_extended_usage(c), sshbuf_len(c->extended),
+ c->rfd, c->wfd, c->efd, c->sock, c->ctl_chan,
+ c->have_ctl_child_id ? "c" : "nc", c->ctl_child_id,
+- c->io_want, c->io_ready);
++ c->io_want, c->io_ready,
++ c->isatty ? "T" : "", c->bulk ? "B" : "I");
+ return ret;
+ }
+
+@@ -2606,10 +2641,13 @@ channel_handler(struct ssh *ssh, int table, struct
timespec *timeout)
+ time_t now;
+
+ now = monotime();
+- for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
++ for (sc->nbulk = i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
+ c = sc->channels[i];
+ if (c == NULL)
+ continue;
++ /* Count open channels in bulk state */
++ if (c->type == SSH_CHANNEL_OPEN && c->bulk)
++ sc->nbulk++;
+ /* Try to keep IO going while rekeying */
+ if (ssh_packet_is_rekeying(ssh) && c->type != SSH_CHANNEL_OPEN)
+ continue;
+diff --git a/channels.h b/channels.h
+index 134528d59..a84c9dfdd 100644
+--- a/channels.h
++++ b/channels.h
+@@ -82,6 +82,10 @@
+ #define FORWARD_ADM 0x100
+ #define FORWARD_USER 0x101
+
++/* default pattern-lists used to classify channel types as bulk */
++#define CHANNEL_BULK_TTY ""
++#define CHANNEL_BULK_NOTTY "direct-*,forwarded-*,tun-*,x11-*,session*"
++
+ struct ssh;
+ struct Channel;
+ typedef struct Channel Channel;
+@@ -180,6 +184,7 @@ struct Channel {
+
+ char *ctype; /* const type - NB. not freed on channel_free */
+ char *xctype; /* extended type */
++ int bulk; /* channel is non-interactive */
+
+ /* callback */
+ channel_open_fn *open_confirm;
+@@ -289,6 +294,7 @@ Channel *channel_new(struct ssh *, char *, int, int, int,
int,
+ u_int, u_int, int, const char *, int);
+ void channel_set_fds(struct ssh *, int, int, int, int, int,
+ int, int, u_int);
++void channel_set_tty(struct ssh *, Channel *);
+ void channel_free(struct ssh *, Channel *);
+ void channel_free_all(struct ssh *);
+ void channel_stop_listening(struct ssh *);
+@@ -308,6 +314,7 @@ void channel_register_status_confirm(struct ssh *,
int,
+ void channel_cancel_cleanup(struct ssh *, int);
+ int channel_close_fd(struct ssh *, Channel *, int *);
+ void channel_send_window_changes(struct ssh *);
++int channel_has_bulk(struct ssh *);
+
+ /* channel inactivity timeouts */
+ void channel_add_timeout(struct ssh *, const char *, int);
+diff --git a/clientloop.c b/clientloop.c
+index 407b76b4c..34a94fdb5 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -1451,7 +1451,7 @@ client_loop(struct ssh *ssh, int have_pty, int
escape_char_arg,
+ struct pollfd *pfd = NULL;
+ u_int npfd_alloc = 0, npfd_active = 0;
+ double start_time, total_time;
+- int channel_did_enqueue = 0, r;
++ int interactive = -1, channel_did_enqueue = 0, r;
+ u_int64_t ibytes, obytes;
+ int conn_in_ready, conn_out_ready;
+ sigset_t bsigset, osigset;
+@@ -1620,6 +1620,12 @@ client_loop(struct ssh *ssh, int have_pty, int
escape_char_arg,
+ * sender.
+ */
+ if (conn_out_ready) {
++ if (interactive != !channel_has_bulk(ssh)) {
++ interactive = !channel_has_bulk(ssh);
++ debug2_f("session QoS is now %s", interactive ?
++ "interactive" : "non-interactive");
++ ssh_packet_set_interactive(ssh, interactive);
++ }
+ if ((r = ssh_packet_write_poll(ssh)) != 0) {
+ sshpkt_fatal(ssh, r,
+ "%s: ssh_packet_write_poll", __func__);
+@@ -2704,9 +2710,6 @@ client_session2_setup(struct ssh *ssh, int id, int
want_tty, int want_subsystem,
+ if ((c = channel_lookup(ssh, id)) == NULL)
+ fatal_f("channel %d: unknown channel", id);
+
+- ssh_packet_set_interactive(ssh, want_tty,
+- options.ip_qos_interactive, options.ip_qos_bulk);
+-
+ if (want_tty) {
+ struct winsize ws;
+
+diff --git a/misc.c b/misc.c
+index 3ff464573..28c3f150d 100644
+--- a/misc.c
++++ b/misc.c
+@@ -297,6 +297,10 @@ set_sock_tos(int fd, int tos)
+ #ifndef IP_TOS_IS_BROKEN
+ int af;
+
++ if (tos < 0 || tos == INT_MAX) {
++ debug_f("invalid TOS %d", tos);
++ return;
++ }
+ switch ((af = get_sock_af(fd))) {
+ case -1:
+ /* assume not a socket */
+diff --git a/mux.c b/mux.c
+index b48e69a12..c5eaf1007 100644
+--- a/mux.c
++++ b/mux.c
+@@ -460,6 +460,8 @@ mux_master_process_new_session(struct ssh *ssh, u_int rid,
+ nc = channel_new(ssh, "session", SSH_CHANNEL_OPENING,
+ new_fd[0], new_fd[1], new_fd[2], window, packetmax,
+ CHAN_EXTENDED_WRITE, "client-session", CHANNEL_NONBLOCK_STDIO);
++ if (cctx->want_tty)
++ channel_set_tty(ssh, nc);
+
+ nc->ctl_chan = c->self; /* link session -> control channel */
+ c->ctl_child_id = nc->self; /* link control -> session channel */
+diff --git a/packet.c b/packet.c
+index 9dea2cfc5..51135847a 100644
+--- a/packet.c
++++ b/packet.c
+@@ -210,8 +210,8 @@ struct session_state {
+ /* Used in ssh_packet_send_mux() */
+ int mux;
+
+- /* Used in packet_set_interactive */
+- int set_interactive_called;
++ /* QoS handling */
++ int qos_interactive, qos_other;
+
+ /* Used in packet_set_maxsize */
+ int set_maxsize_called;
+@@ -219,6 +219,9 @@ struct session_state {
+ /* One-off warning about weak ciphers */
+ int cipher_warning_done;
+
++ /* Nagle disabled on socket */
++ int nodelay_set;
++
+ /* Hook for fuzzing inbound packets */
+ ssh_packet_hook_fn *hook_in;
+ void *hook_in_ctx;
+@@ -247,6 +250,8 @@ ssh_alloc_session_state(void)
+ state->connection_out = -1;
+ state->max_packet_size = 32768;
+ state->packet_timeout_ms = -1;
++ state->interactive_mode = 1;
++ state->qos_interactive = state->qos_other = -1;
+ state->p_send.packets = state->p_read.packets = 0;
+ state->initialized = 1;
+ /*
+@@ -2206,37 +2211,44 @@ ssh_packet_interactive_data_to_write(struct ssh *ssh)
+ sshbuf_len(ssh->state->output) < 256;
+ }
+
+-void
+-ssh_packet_set_tos(struct ssh *ssh, int tos)
++static void
++apply_qos(struct ssh *ssh)
+ {
+- if (!ssh_packet_connection_is_on_socket(ssh) || tos == INT_MAX)
++ struct session_state *state = ssh->state;
++ int qos = state->interactive_mode ?
++ state->qos_interactive : state->qos_other;
++
++ if (!ssh_packet_connection_is_on_socket(ssh))
+ return;
+- set_sock_tos(ssh->state->connection_in, tos);
++ if (!state->nodelay_set) {
++ set_nodelay(state->connection_in);
++ state->nodelay_set = 1;
++ }
++ set_sock_tos(ssh->state->connection_in, qos);
+ }
+
+-/* Informs that the current session is interactive. Sets IP flags for that.
*/
+-
++/* Informs that the current session is interactive. */
+ void
+-ssh_packet_set_interactive(struct ssh *ssh, int interactive, int
qos_interactive, int qos_bulk)
++ssh_packet_set_interactive(struct ssh *ssh, int interactive)
+ {
+ struct session_state *state = ssh->state;
+
+- if (state->set_interactive_called)
+- return;
+- state->set_interactive_called = 1;
+-
+- /* Record that we are in interactive mode. */
+ state->interactive_mode = interactive;
++ apply_qos(ssh);
++}
+
+- /* Only set socket options if using a socket. */
+- if (!ssh_packet_connection_is_on_socket(ssh))
+- return;
+- set_nodelay(state->connection_in);
+- ssh_packet_set_tos(ssh, interactive ? qos_interactive : qos_bulk);
++/* Set QoS flags to be used for interactive and non-interactive sessions */
++void
++ssh_packet_set_qos(struct ssh *ssh, int qos_interactive, int qos_other)
++{
++ struct session_state *state = ssh->state;
++
++ state->qos_interactive = qos_interactive;
++ state->qos_other = qos_other;
++ apply_qos(ssh);
+ }
+
+ /* Returns true if the current connection is interactive. */
+-
+ int
+ ssh_packet_is_interactive(struct ssh *ssh)
+ {
+@@ -2415,6 +2427,7 @@ ssh_packet_get_state(struct ssh *ssh, struct sshbuf *m)
+ struct session_state *state = ssh->state;
+ int r;
+
++#define ENCODE_INT(v) (((v) < 0) ? 0xFFFFFFFF : (u_int)v)
+ if ((r = kex_to_blob(m, ssh->kex)) != 0 ||
+ (r = newkeys_to_blob(m, ssh, MODE_OUT)) != 0 ||
+ (r = newkeys_to_blob(m, ssh, MODE_IN)) != 0 ||
+@@ -2429,9 +2442,12 @@ ssh_packet_get_state(struct ssh *ssh, struct sshbuf *m)
+ (r = sshbuf_put_u32(m, state->p_read.packets)) != 0 ||
+ (r = sshbuf_put_u64(m, state->p_read.bytes)) != 0 ||
+ (r = sshbuf_put_stringb(m, state->input)) != 0 ||
+- (r = sshbuf_put_stringb(m, state->output)) != 0)
++ (r = sshbuf_put_stringb(m, state->output)) != 0 ||
++ (r = sshbuf_put_u32(m, ENCODE_INT(state->interactive_mode))) != 0 ||
++ (r = sshbuf_put_u32(m, ENCODE_INT(state->qos_interactive))) != 0 ||
++ (r = sshbuf_put_u32(m, ENCODE_INT(state->qos_other))) != 0)
+ return r;
+-
++#undef ENCODE_INT
+ return 0;
+ }
+
+@@ -2550,6 +2566,7 @@ ssh_packet_set_state(struct ssh *ssh, struct sshbuf *m)
+ const u_char *input, *output;
+ size_t ilen, olen;
+ int r;
++ u_int interactive, qos_interactive, qos_other;
+
+ if ((r = kex_from_blob(m, &ssh->kex)) != 0 ||
+ (r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 ||
+@@ -2586,6 +2603,16 @@ ssh_packet_set_state(struct ssh *ssh, struct sshbuf *m)
+ (r = sshbuf_put(state->output, output, olen)) != 0)
+ return r;
+
++ if ((r = sshbuf_get_u32(m, &interactive)) != 0 ||
++ (r = sshbuf_get_u32(m, &qos_interactive)) != 0 ||
++ (r = sshbuf_get_u32(m, &qos_other)) != 0)
++ return r;
++#define DECODE_INT(v) ((v) > INT_MAX ? -1 : (v))
++ state->interactive_mode = DECODE_INT(interactive);
++ state->qos_interactive = DECODE_INT(qos_interactive);
++ state->qos_other = DECODE_INT(qos_other);
++#undef DECODE_INT
++
+ if (sshbuf_len(m))
+ return SSH_ERR_INVALID_FORMAT;
+ debug3_f("done");
+diff --git a/packet.h b/packet.h
+index 49bb87f07..85d292aa1 100644
+--- a/packet.h
++++ b/packet.h
+@@ -111,8 +111,9 @@ int ssh_packet_check_rekey(struct ssh *);
+ void ssh_packet_set_protocol_flags(struct ssh *, u_int);
+ u_int ssh_packet_get_protocol_flags(struct ssh *);
+ void ssh_packet_set_tos(struct ssh *, int);
+-void ssh_packet_set_interactive(struct ssh *, int, int, int);
++void ssh_packet_set_interactive(struct ssh *, int);
+ int ssh_packet_is_interactive(struct ssh *);
++void ssh_packet_set_qos(struct ssh *, int, int);
+ void ssh_packet_set_server(struct ssh *);
+ void ssh_packet_set_authenticated(struct ssh *);
+ void ssh_packet_set_mux(struct ssh *);
+diff --git a/serverloop.c b/serverloop.c
+index 40ddfb042..395a7e317 100644
+--- a/serverloop.c
++++ b/serverloop.c
+@@ -285,8 +285,15 @@ static void
+ process_output(struct ssh *ssh, int connection_out)
+ {
+ int r;
++ static int interactive = -1;
+
+ /* Send any buffered packet data to the client. */
++ if (interactive != !channel_has_bulk(ssh)) {
++ interactive = !channel_has_bulk(ssh);
++ debug2_f("session QoS is now %s", interactive ?
++ "interactive" : "non-interactive");
++ ssh_packet_set_interactive(ssh, interactive);
++ }
+ if ((r = ssh_packet_write_poll(ssh)) != 0) {
+ sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll",
+ __func__);
+diff --git a/session.c b/session.c
+index 6614d8560..dfa97ee1c 100644
+--- a/session.c
++++ b/session.c
+@@ -524,9 +524,6 @@ do_exec_no_pty(struct ssh *ssh, Session *s, const char
*command)
+ #endif
+
+ s->pid = pid;
+- /* Set interactive/non-interactive mode. */
+- ssh_packet_set_interactive(ssh, s->display != NULL,
+- options.ip_qos_interactive, options.ip_qos_bulk);
+
+ /*
+ * Clear loginmsg, since it's the child's responsibility to display
+@@ -654,8 +651,6 @@ do_exec_pty(struct ssh *ssh, Session *s, const char
*command)
+
+ /* Enter interactive session. */
+ s->ptymaster = ptymaster;
+- ssh_packet_set_interactive(ssh, 1,
+- options.ip_qos_interactive, options.ip_qos_bulk);
+ session_set_fds(ssh, s, ptyfd, fdout, -1, 1, 1);
+ return 0;
+ }
+diff --git a/ssh.c b/ssh.c
+index e668fb771..ff60ae569 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -710,7 +710,6 @@ main(int ac, char **av)
+ fatal("Couldn't allocate session state");
+ channel_init_channels(ssh);
+
+-
+ /* Parse command-line arguments. */
+ args = argv_assemble(ac, av); /* logged later */
+ host = NULL;
+@@ -1344,6 +1343,8 @@ main(int ac, char **av)
+ if (options.port == 0)
+ options.port = default_ssh_port();
+ channel_set_af(ssh, options.address_family);
++ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
++ options.ip_qos_bulk);
+
+ /* Tidy and check options */
+ if (options.host_key_alias != NULL)
+@@ -2168,7 +2169,7 @@ ssh_session2_setup(struct ssh *ssh, int id, int success,
void *arg)
+ {
+ extern char **environ;
+ const char *display, *term;
+- int r, interactive = tty_flag;
++ int r;
+ char *proto = NULL, *data = NULL;
+
+ if (!success)
+@@ -2187,7 +2188,6 @@ ssh_session2_setup(struct ssh *ssh, int id, int success,
void *arg)
+ data, 1);
+ client_expect_confirm(ssh, id, "X11 forwarding", CONFIRM_WARN);
+ /* XXX exit_on_forward_failure */
+- interactive = 1;
+ }
+
+ check_agent_present();
+@@ -2198,10 +2198,6 @@ ssh_session2_setup(struct ssh *ssh, int id, int
success, void *arg)
+ fatal_fr(r, "send packet");
+ }
+
+- /* Tell the packet module whether this is an interactive session. */
+- ssh_packet_set_interactive(ssh, interactive,
+- options.ip_qos_interactive, options.ip_qos_bulk);
+-
+ if ((term = lookup_env_in_list("TERM", options.setenv,
+ options.num_setenv)) == NULL || *term == '\0')
+ term = getenv("TERM");
+@@ -2238,8 +2234,9 @@ ssh_session2_open(struct ssh *ssh)
+ "session", SSH_CHANNEL_OPENING, in, out, err,
+ window, packetmax, CHAN_EXTENDED_WRITE,
+ "client-session", CHANNEL_NONBLOCK_STDIO);
+-
+- debug3_f("channel_new: %d", c->self);
++ if (tty_flag)
++ channel_set_tty(ssh, c);
++ debug3_f("channel_new: %d%s", c->self, tty_flag ? " (tty)" : "");
+
+ channel_send_open(ssh, c->self);
+ if (options.session_type != SESSION_TYPE_NONE)
+@@ -2252,7 +2249,7 @@ ssh_session2_open(struct ssh *ssh)
+ static int
+ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
+ {
+- int r, interactive, id = -1;
++ int r, id = -1;
+ char *cp, *tun_fwd_ifname = NULL;
+
+ /* XXX should be pre-session */
+@@ -2308,14 +2305,6 @@ ssh_session2(struct ssh *ssh, const struct
ssh_conn_info *cinfo)
+
+ if (options.session_type != SESSION_TYPE_NONE)
+ id = ssh_session2_open(ssh);
+- else {
+- interactive = options.control_master == SSHCTL_MASTER_NO;
+- /* ControlPersist may have clobbered ControlMaster, so check */
+- if (need_controlpersist_detach)
+- interactive = otty_flag != 0;
+- ssh_packet_set_interactive(ssh, interactive,
+- options.ip_qos_interactive, options.ip_qos_bulk);
+- }
+
+ /* If we don't expect to open a new session, then disallow it */
+ if (options.control_master == SSHCTL_MASTER_NO &&
+diff --git a/sshd-auth.c b/sshd-auth.c
+index 362abc92c..371a4b795 100644
+--- a/sshd-auth.c
++++ b/sshd-auth.c
+@@ -672,6 +672,8 @@ main(int ac, char **av)
+ /* Fill in default values for those options not explicitly set. */
+ fill_default_server_options(&options);
+ options.timing_secret = timing_secret; /* XXX eliminate from unpriv */
++ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
++ options.ip_qos_bulk);
+
+ /* Reinit logging in case config set Level, Facility or Verbose. */
+ log_init(__progname, options.log_level, options.log_facility, 1);
+diff --git a/sshd-session.c b/sshd-session.c
+index 2b6d2a98b..989e21734 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -1215,6 +1215,8 @@ main(int ac, char **av)
+ fatal("Unable to create connection");
+ the_active_state = ssh;
+ ssh_packet_set_server(ssh);
++ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
++ options.ip_qos_bulk);
+
+ check_ip_options(ssh);
+
diff -Nru openssh-10.0p1/debian/patches/ipqos-set-extended-type.patch
openssh-10.0p1/debian/patches/ipqos-set-extended-type.patch
--- openssh-10.0p1/debian/patches/ipqos-set-extended-type.patch 1970-01-01
01:00:00.000000000 +0100
+++ openssh-10.0p1/debian/patches/ipqos-set-extended-type.patch 2026-05-01
17:26:08.000000000 +0100
@@ -0,0 +1,68 @@
+From 4207d8a7a4060cad77ec1b78ff08f3e0546c4fbd Mon Sep 17 00:00:00 2001
+From: "[email protected]" <[email protected]>
+Date: Sun, 19 Apr 2026 23:37:22 +0000
+Subject: upstream: correctly set extended type for client-side channels.
+
+Fixes interactive vs bulk IPQoS for client->server traffic. ok job@
+
+OpenBSD-Commit-ID: 34f5131face8d6dc4ae6955196e5fcafb3570cfe
+
+Origin: upstream,
https://anongit.mindrot.org/openssh.git/commit/?id=45b30e0a5439a02417a4fe982a4b16a9c126ba6b
+Last-Update: 2026-05-03
+
+Patch-Name: ipqos-set-extended-type.patch
+---
+ clientloop.c | 16 +++++++++++-----
+ 1 file changed, 11 insertions(+), 5 deletions(-)
+
+diff --git a/clientloop.c b/clientloop.c
+index 34a94fdb5..5979d5803 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -2702,7 +2702,7 @@ client_session2_setup(struct ssh *ssh, int id, int
want_tty, int want_subsystem,
+ {
+ size_t i, j, len;
+ int matched, r;
+- char *name, *val;
++ char *type = NULL, *cmdstring = NULL, *name, *val;
+ Channel *c = NULL;
+
+ debug2_f("id %d", id);
+@@ -2777,19 +2777,21 @@ client_session2_setup(struct ssh *ssh, int id, int
want_tty, int want_subsystem,
+
+ len = sshbuf_len(cmd);
+ if (len > 0) {
++ if ((cmdstring = sshbuf_dup_string(cmd)) == NULL)
++ fatal_f("sshbuf_dup_string failed");
+ if (len > 900)
+ len = 900;
+ if (want_subsystem) {
+- debug("Sending subsystem: %.*s",
+- (int)len, (const u_char*)sshbuf_ptr(cmd));
++ debug("Sending subsystem: %.*s", (int)len, cmdstring);
+ channel_request_start(ssh, id, "subsystem", 1);
+ client_expect_confirm(ssh, id, "subsystem",
+ CONFIRM_CLOSE);
++ xasprintf(&type, "session:subsystem:%s", cmdstring);
+ } else {
+- debug("Sending command: %.*s",
+- (int)len, (const u_char*)sshbuf_ptr(cmd));
++ debug("Sending command: %.*s", (int)len, cmdstring);
+ channel_request_start(ssh, id, "exec", 1);
+ client_expect_confirm(ssh, id, "exec", CONFIRM_CLOSE);
++ xasprintf(&type, "session:command");
+ }
+ if ((r = sshpkt_put_stringb(ssh, cmd)) != 0 ||
+ (r = sshpkt_send(ssh)) != 0)
+@@ -2799,7 +2801,11 @@ client_session2_setup(struct ssh *ssh, int id, int
want_tty, int want_subsystem,
+ client_expect_confirm(ssh, id, "shell", CONFIRM_CLOSE);
+ if ((r = sshpkt_send(ssh)) != 0)
+ fatal_fr(r, "send shell");
++ xasprintf(&type, "session:shell");
+ }
++ channel_set_xtype(ssh, id, type);
++ free(cmdstring);
++ free(type);
+
+ session_setup_complete = 1;
+ client_repledge();
diff -Nru openssh-10.0p1/debian/patches/pam-avoid-unknown-host.patch
openssh-10.0p1/debian/patches/pam-avoid-unknown-host.patch
--- openssh-10.0p1/debian/patches/pam-avoid-unknown-host.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/pam-avoid-unknown-host.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From ccbb3efb1598cde11bb76d6045cd73c8f1773fd0 Mon Sep 17 00:00:00 2001
+From f9d483dc7e0e250e7e9f2117c129c768239827c3 Mon Sep 17 00:00:00 2001
From: Daan De Meyer <[email protected]>
Date: Mon, 20 Mar 2023 20:22:14 +0100
Subject: Only set PAM_RHOST if the remote host is not "UNKNOWN"
diff -Nru openssh-10.0p1/debian/patches/regress-conch-dev-zero.patch
openssh-10.0p1/debian/patches/regress-conch-dev-zero.patch
--- openssh-10.0p1/debian/patches/regress-conch-dev-zero.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/regress-conch-dev-zero.patch 2026-05-01
17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 9db329d6764879915981e5ace3acd02534922b1d Mon Sep 17 00:00:00 2001
+From f5998f35b86e5927add370e915ae0ed0c2a4e8f6 Mon Sep 17 00:00:00 2001
From: Colin Watson <[email protected]>
Date: Sun, 31 Mar 2024 00:24:11 +0000
Subject: regress: Redirect conch stdin from /dev/zero
diff -Nru openssh-10.0p1/debian/patches/revert-ipqos-defaults.patch
openssh-10.0p1/debian/patches/revert-ipqos-defaults.patch
--- openssh-10.0p1/debian/patches/revert-ipqos-defaults.patch 2026-04-05
00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/revert-ipqos-defaults.patch 1970-01-01
01:00:00.000000000 +0100
@@ -1,93 +0,0 @@
-From 88dc4a66e9c8fd350152080713f33e26fd7df202 Mon Sep 17 00:00:00 2001
-From: Colin Watson <[email protected]>
-Date: Mon, 8 Apr 2019 10:46:29 +0100
-Subject: Revert "upstream: Update default IPQoS in ssh(1), sshd(8) to DSCP
- AF21 for"
-
-This reverts commit 5ee8448ad7c306f05a9f56769f95336a8269f379.
-
-The IPQoS default changes have some unfortunate interactions with
-iptables (see https://bugs.debian.org/923880) and VMware, so I'm
-temporarily reverting them until those have been fixed.
-
-Bug-Debian: https://bugs.debian.org/923879
-Bug-Debian: https://bugs.debian.org/926229
-Bug-Ubuntu: https://bugs.launchpad.net/bugs/1822370
-Last-Update: 2019-04-08
-
-Patch-Name: revert-ipqos-defaults.patch
----
- readconf.c | 4 ++--
- servconf.c | 4 ++--
- ssh_config.5 | 6 ++----
- sshd_config.5 | 6 ++----
- 4 files changed, 8 insertions(+), 12 deletions(-)
-
-diff --git a/readconf.c b/readconf.c
-index fc625a00c..09b8ca33c 100644
---- a/readconf.c
-+++ b/readconf.c
-@@ -3012,9 +3012,9 @@ fill_default_options(Options * options)
- if (options->visual_host_key == -1)
- options->visual_host_key = 0;
- if (options->ip_qos_interactive == -1)
-- options->ip_qos_interactive = IPTOS_DSCP_AF21;
-+ options->ip_qos_interactive = IPTOS_LOWDELAY;
- if (options->ip_qos_bulk == -1)
-- options->ip_qos_bulk = IPTOS_DSCP_CS1;
-+ options->ip_qos_bulk = IPTOS_THROUGHPUT;
- if (options->request_tty == -1)
- options->request_tty = REQUEST_TTY_AUTO;
- if (options->session_type == -1)
-diff --git a/servconf.c b/servconf.c
-index 4891a43d6..a51370a6d 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -485,9 +485,9 @@ fill_default_server_options(ServerOptions *options)
- if (options->permit_tun == -1)
- options->permit_tun = SSH_TUNMODE_NO;
- if (options->ip_qos_interactive == -1)
-- options->ip_qos_interactive = IPTOS_DSCP_AF21;
-+ options->ip_qos_interactive = IPTOS_LOWDELAY;
- if (options->ip_qos_bulk == -1)
-- options->ip_qos_bulk = IPTOS_DSCP_CS1;
-+ options->ip_qos_bulk = IPTOS_THROUGHPUT;
- if (options->version_addendum == NULL)
- options->version_addendum = xstrdup("");
- if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1)
-diff --git a/ssh_config.5 b/ssh_config.5
-index d8452237d..df12ef118 100644
---- a/ssh_config.5
-+++ b/ssh_config.5
-@@ -1367,11 +1367,9 @@ If one argument is specified, it is used as the packet
class unconditionally.
- If two values are specified, the first is automatically selected for
- interactive sessions and the second for non-interactive sessions.
- The default is
--.Cm af21
--(Low-Latency Data)
-+.Cm lowdelay
- for interactive sessions and
--.Cm cs1
--(Lower Effort)
-+.Cm throughput
- for non-interactive sessions.
- .It Cm KbdInteractiveAuthentication
- Specifies whether to use keyboard-interactive authentication.
-diff --git a/sshd_config.5 b/sshd_config.5
-index a5594102f..998837edf 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -1022,11 +1022,9 @@ If one argument is specified, it is used as the packet
class unconditionally.
- If two values are specified, the first is automatically selected for
- interactive sessions and the second for non-interactive sessions.
- The default is
--.Cm af21
--(Low-Latency Data)
-+.Cm lowdelay
- for interactive sessions and
--.Cm cs1
--(Lower Effort)
-+.Cm throughput
- for non-interactive sessions.
- .It Cm KbdInteractiveAuthentication
- Specifies whether to allow keyboard-interactive authentication.
diff -Nru openssh-10.0p1/debian/patches/series
openssh-10.0p1/debian/patches/series
--- openssh-10.0p1/debian/patches/series 2026-04-05 00:27:07.000000000
+0100
+++ openssh-10.0p1/debian/patches/series 2026-05-01 17:26:08.000000000
+0100
@@ -20,7 +20,6 @@
gnome-ssh-askpass2-icon.patch
debian-config.patch
restore-authorized_keys2.patch
-revert-ipqos-defaults.patch
systemd-socket-activation.patch
skip-utimensat-test-on-zfs.patch
regress-conch-dev-zero.patch
@@ -30,3 +29,14 @@
CVE-2025-61985.patch
CVE-2025-61984-tests.patch
fix-max-startups-tracking.patch
+CVE-2026-35388.patch
+CVE-2026-35385.patch
+CVE-2026-35387.patch
+CVE-2026-35414.patch
+CVE-2026-35386-1.patch
+CVE-2026-35386-2.patch
+CVE-2026-35386-3.patch
+ipqos-interactive-ef.patch
+ipqos-deprecate-tos-keywords.patch
+ipqos-set-at-runtime.patch
+ipqos-set-extended-type.patch
diff -Nru openssh-10.0p1/debian/patches/skip-utimensat-test-on-zfs.patch
openssh-10.0p1/debian/patches/skip-utimensat-test-on-zfs.patch
--- openssh-10.0p1/debian/patches/skip-utimensat-test-on-zfs.patch
2026-04-05 00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/skip-utimensat-test-on-zfs.patch
2026-05-01 17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 3d83f47df49d9b38dd014ef87089b14b42060250 Mon Sep 17 00:00:00 2001
+From 0bed1d6ebd6374ee22c3e3e25b27b044f4e95ae2 Mon Sep 17 00:00:00 2001
From: Colin Watson <[email protected]>
Date: Mon, 11 Mar 2024 16:24:49 +0000
Subject: Skip utimensat test on ZFS
diff -Nru openssh-10.0p1/debian/patches/systemd-socket-activation.patch
openssh-10.0p1/debian/patches/systemd-socket-activation.patch
--- openssh-10.0p1/debian/patches/systemd-socket-activation.patch
2026-04-05 00:27:07.000000000 +0100
+++ openssh-10.0p1/debian/patches/systemd-socket-activation.patch
2026-05-01 17:26:08.000000000 +0100
@@ -1,4 +1,4 @@
-From 7f825ab75842dd91ad2ac00acabc5ea0350c6794 Mon Sep 17 00:00:00 2001
+From 3785943259d3d91473571aea827e3b09ecc53a23 Mon Sep 17 00:00:00 2001
From: Steve Langasek <[email protected]>
Date: Thu, 1 Sep 2022 16:03:37 +0100
Subject: Support systemd socket activation
--- End Message ---