Where
-Infinity
0
Severity
10
AV:N/AC:L/Au:N/C:C/I:C/A:C

A system is running a version of software that was replaced with a Trojan Horse at one of its distribution points, such as (1) TCP Wrappers 7.6, (2) util-linux 2.9g, (3) wuarchive ftpd (wuftpd) 2.2 and 2.1f, (4) IRC client (ircII) ircII 2.2.9, (5) OpenSSH 3.4p1, or (6) Sendmail 8.12.6.

First published (updated )
Severity
9.4
Use After Free
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:L

Last updated 13 July 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
7

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).

First published (updated )
Severity
6.5
AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N

Last updated 22 September 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
6.5
Double Free
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

OpenSSH server (sshd) 9.1 introduced a double-free vulnerability during options.kexalgorithms handling. This is fixed in OpenSSH 9.2. The double free can be leveraged, by an unauthenticated remote attacker in the default configuration, to jump to any location in the sshd address space. One third-party report states "remote code execution is theoretically possible."

First published (updated )
Severity
6.1
AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:L/A:N

A flaw was found in OpenSSH. A local unprivileged attacker on a Linux client host can hijack client-side X11 forwarding connections. This is possible by pre-binding the preferred abstract X socket name when X11 forwarding is enabled and a local UNIX-domain X socket is used. A successful attack can compromise the confidentiality of forwarded X11 traffic, including sensitive window contents and input, and may allow some manipulation of the forwarded session.

1 / 3
Source: MITRE
First published (updated )
Severity
5.4
AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N

internal-sftp in sshd in OpenSSH before 10.4 recognizes only the first 9 command-line arguments, which can be important if a later command-line argument would have helped to ensure the intended security properties of an SFTP connection.

1 / 2
Source: MITRE
First published (updated )
Severity
5.4
AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:L

Last updated 13 July 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
5.4
AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:L

Last updated 22 September 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
4.5
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N

ssh in OpenSSH before 10.1 allows the '\0' character in an ssh:// URI, potentially leading to code execution when a ProxyCommand is used.

First published (updated )
Severity
4

ssh in OpenSSH before 10.1 allows the '\0' character in an ssh:// URI, potentially leading to code execution when a ProxyCommand is used.

First published (updated )
Severity
4

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.)

First published (updated )
Severity
4

The API function sshgethexa() is vulnerable, when 0-lenght input is provided to this function. This function is used internally in sshgetfingerprinthash() and sshprinthexa() (deprecated), which is vulnerable to the same input (length is provided by the calling application).

The function is also used internally in the gssapi code for logging the OIDs received by the server during GSSAPI authentication. This could be triggered remotely, when the server allows GSSAPI authentication and logging verbosity is set at least to SSHLOGPACKET (3). This could cause self-DoS of the per-connection daemon process.

First published (updated )
Severity
4

A malicious SCP server can send unexpected paths that could make the client application override local files outside of working directory. This could be misused to create malicious executable or configuration files and make the user execute them under specific consequences.

This is the same issue as in OpenSSH, tracked as CVE-2019-6111.

First published (updated )
Severity
4

OpenSSH before 10.3 mishandles the authorizedkeys principals option in uncommon scenarios involving a principals list in conjunction with a Certificate Authority that makes certain use of comma characters.

First published (updated )
Severity
4

AIONLYREPORT package: openssh-9.9p1-22.el102 ------ Summary: Local MITM of X11 forwarding via preferred Linux abstract UNIX socket connection: a local unprivileged process on the Linux client host can hijack the client-side X11 forwarding connection by pre-binding the preferred abstract X socket name. Requirements to exploit: An attacker needs local unprivileged code execution on the Linux host running the OpenSSH client, client-side X11 forwarding enabled (-X, -Y, or equivalent configuration), a local DISPLAY that resolves to a UNIX-domain X socket such as :0 or unix:0, and a forwarded X11 connection to be opened while the attacker has pre-bound the matching abstract socket name. Component affected: openssh-9.9p1-22.el102, client-side X11 forwarding code in openssh-9.9p1/channels.c (connectlocalxsocket(), x11openhelper()). Version affected: openssh-9.9p1-22.el102; reachable on Linux in the client-side X11 forwarding path when X11 forwarding is enabled and DISPLAY resolves to a local UNIX-domain X socket Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:L/A:N - 5.3 (MEDIUM) AV:L - The attacker must already have local code execution on the Linux system running the OpenSSH client. AC:H - Exploitation depends on a feature-specific client configuration, successful pre-binding of the matching abstract X11 socket, and a forwarded X11 connection actually being opened. PR:L - An unprivileged local user account or equivalent local process execution is sufficient. UI:R - A user must enable X11 forwarding and the session must trigger a forwarded X11 connection. S:U - The impact stays within the client-side OpenSSH/X11 forwarding security scope. C:H - A successful hijack can expose forwarded X11 traffic, including sensitive window contents and input; when real xauth data is available, the saved X11 authentication data may also be substituted into the hijacked stream. I:L - The attacker can influence the X11 protocol stream presented to the forwarded application, but the demonstrated effect is narrower than general host compromise. A:N - The issue does not require a direct availability impact to succeed. Impact: Moderate. A successful attack can compromise the confidentiality of forwarded X11 traffic and allow some integrity impact on the forwarded session, but exploitation is limited to a local attacker on the client host and depends on the non-default, feature-specific condition that X11 forwarding is enabled and used. Under Red Hat’s criteria, this is better classified as Moderate than Important because it is real but not easily exploitable in typical deployments. Embargo: no Reason: This is a local, configuration-dependent client-side issue with straightforward mitigation by disabling X11 forwarding where it is not required, and it does not present an easily exploitable remote compromise path. Acknowledgement: Aisle Research Vulnerability Details: This issue is in the OpenSSH client’s Linux X11 forwarding path. When the client receives an X11 open request, the call path reaches x11connectdisplay(), which parses DISPLAY and, for local UNIX-domain displays such as :0 or unix:0, calls connectlocalxsocket(). In the vulnerable path, Linux tries the abstract UNIX socket name before the filesystem socket: c len = snprintf(buf + 1, sizeof (buf) - 1, PATHUNIXX, dnr); #ifdef linux / try abstract socket first / buf[0] = '\0'; if ((ret = connectlocalxsocketpath(buf, len + 1)) >= 0) return ret; #endif if ((ret = connectlocalxsocketpath(buf + 1, len)) >= 0) return ret; PATHUNIXX resolves to /tmp/.X11-unix/X%u, so on Linux the first attempted endpoint is the abstract socket name \0/tmp/.X11-unix/X<display>. Because Linux abstract UNIX sockets are not mediated by filesystem permissions, an unprivileged local process can pre-bind that name and receive the forwarded X11 connection before the legitimate filesystem socket. The connection helper performs a direct connect() and does not verify peer credentials or ownership after the connection succeeds. OpenSSH’s X11 spoofing logic validates the fake cookie received from the remote side, but it does not authenticate the local X11 endpoint. After the fake cookie check passes, the client replaces it with the saved X11 authentication data: c if (datalen != sc->x11fakedatalen || timingsafebcmp(ucp + 12 + ((protolen + 3) & ~3), sc->x11fakedata, sc->x11fakedatalen) != 0) { debug2("X11 auth data does not match fake data."); return -1; } ... memcpy(ucp + 12 + ((protolen + 3) & ~3), sc->x11saveddata, sc->x11saveddatalen); As a result, an attacker-controlled abstract socket can receive the forwarded X11 session first. Where real xauth data was obtained, the rewritten first packet will contain the saved X11 authentication data. If xauth data is unavailable, OpenSSH can fall back to generated fake data, which reduces credential-leak impact but does not prevent the connection hijack or interception of the forwarded X11 stream. The available evidence supports interception and limited manipulation of forwarded X11 sessions, not arbitrary code execution or privilege escalation in the client itself. Steps to reproduce: 1. On a Linux system running the OpenSSH client, confirm the local display uses a UNIX-domain socket such as :0 or unix:0. 2. Before opening a forwarded X11 connection, start a local unprivileged listener bound to the matching abstract socket name. For display :0: python import socket s = socket.socket(socket.AFUNIX, socket.SOCKSTREAM) s.bind("\0/tmp/.X11-unix/X0") s.listen(1) c, = s.accept() print("accepted; first bytes:", c.recv(64).hex()) 3. In another terminal, start an SSH session with X11 forwarding enabled: bash ssh -X user@remote 4. From the remote shell, trigger an X11 application so that the client opens the forwarded X11 channel, for example: bash xclock 5. Observe that the attacker listener accepts the connection and receives the initial X11 bytes before the legitimate filesystem socket /tmp/.X11-unix/X0 is used. 6. Optional confirmation: bash strace -f -e connect ssh -X user@remote Expected result: tracing shows a successful AFUNIX connect to the abstract socket name \0/tmp/.X11-unix/X0 before any successful connect to /tmp/.X11-unix/X0. Mitigation: Disable X11 forwarding on affected clients when it is not required, for example by avoiding -X/-Y and using ForwardX11 no. On shared Linux systems where untrusted local users or processes may be present, avoid relying on client-side X11 forwarding until a fix that prefers the filesystem X socket has been applied. Proposed Fix: Prefer the filesystem X11 socket first, and retain Linux abstract sockets only as a compatibility fallback. diff diff --git a/openssh-9.9p1/channels.c b/openssh-9.9p1/channels.c @@ static int connectlocalxsocket(uint dnr) len = snprintf(buf + 1, sizeof (buf) - 1, PATHUNIXX, dnr); + / Prefer filesystem socket (permission/ownership mediated) / + if ((ret = connectlocalxsocketpath(buf + 1, len)) >= 0) + return ret; #ifdef linux - / try abstract socket first / + / Abstract socket fallback for compatibility / buf[0] = '\0'; if ((ret = connectlocalxsocketpath(buf, len + 1)) >= 0) return ret; #endif - if ((ret = connectlocalxsocketpath(buf + 1, len)) >= 0) - return ret; error("connect %.100s: %.100s", buf + 1, strerror(errno)); return -1; ------ This report was generated using AI technology. Always review AI-generated content prior to use

First published (updated )
Severity
4

scp in OpenSSH before 10.4 may place a file in the parent directory of an intended directory when the copy occurs between two remote destinations.

First published (updated )
Severity
4

OpenSSH has a vulnerability in the scp client utility. Due to the scp implementation being derived from 1983 rcp, the server chooses which files/directories are sent to the client. However, scp client only perform cursory validation of the object name returned (only directory traversal attacks are prevented). A malicious scp server can overwrite arbitrary files in the scp client target directory. If recursive operation (-r) is performed, the server can manipulate subdirectories as well (for example overwrite .ssh/authorizedkeys).

External Reference:

https://sintonen.fi/advisories/scp-client-multiple-vulnerabilities.txt

Proposed Patch:

https://sintonen.fi/advisories/scp-name-validator.patch

First published (updated )
Severity
4

A memory exhaustion issue in OpenSSH that can be triggered before user authentication was found. An unauthenticated attacker could consume approx. 400 MB of memory per each connection. The attacker could set up multiple such connections to run out of server’s memory.

Affected versions: openssh-6.8p1, openssh-6.9p1, openssh-7.0p1, openssh-7.1p1, openssh-7.2p1, openssh-7.3p1.

Upstream patch:

http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/kex.c?rev=1.127&content-type=text/x-cvsweb-markup

First published (updated )
Severity
3.6
AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N

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.)

First published (updated )
Severity
1

The function matchpattern() is used to match conditionals in client configuration files or known hosts against the hostname the client is connecting to.

When the configuration file or knownhosts file is controlled by the attacker, connecting to specific hostnames could cause timeouts and resource exhaustion due to the ineffective backtracking of complex regular expressions.

The pattern matching was modified to avoid the needless backtracing.

First published (updated )
Severity
1

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 sshconfig.

First published (updated )
Severity
1

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.

First published (updated )
Severity
1

OpenSSH before 10.3 omits connection multiplexing confirmation for proxy-mode multiplexing sessions.

First published (updated )
Severity
1

AIONLYREPORT package: openssh-10.2p1-10.1.hum1 ------ Summary: Heap out-of-bounds read in GSSAPI indicator cleanup: missing trailing NULL termination in the GSSAPI auth-indicators array allows sentinel-based consumers to read past the allocated pointer array and may trigger an invalid free during cleanup. Requirements to exploit: An attacker needs network access to SSH on openssh-10.2p1-10.1.hum1 built with GSSAPI support and deployed with GSSAPIAuthentication yes; in this tree that option defaults to disabled. The Kerberos environment must provide a ticket containing at least one authenticated auth-indicators value, and execution must then reach either the failed sshgssapiuserok() cleanup path or the GSSAPIIndicators matching path. Component affected: openssh-10.2p1-10.1.hum1, server-side GSSAPI authentication code in gss-serv.c (sshgssapigetindicators()), with sentinel-based consumption in gss-serv.c (sshgssapiuserok()) and gss-serv-krb5.c (sshgssapicheckindicators()) Version affected: openssh-10.2p1-10.1.hum1 when built with GSSAPI support; remote triggerability additionally requires GSSAPIAuthentication yes and a Kerberos deployment that supplies authenticated auth-indicators Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not yet established. CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - 3.7 (LOW) AV:N - the vulnerable path is reachable through network SSH authentication traffic. AC:H - exploitation depends on a GSSAPI-enabled build, GSSAPIAuthentication yes, a Kerberos ticket carrying authenticated auth-indicators, and reaching a sentinel-based consumer path. PR:N - no prior privileges on the target host are required beyond initiating the SSH authentication exchange. UI:N - no separate user interaction is required once the target is configured this way. S:U - the effect is limited to the vulnerable SSH authentication component. C:N - no reliable confidentiality impact is established from the available evidence. I:N - no integrity impact is established from the available evidence. A:L - the demonstrated consequence is limited to out-of-bounds reads that may crash or abort the affected authentication path. Impact: Moderate. Red Hat classifies easily triggered remote denial of service issues as Important, but the trigger conditions here are materially narrower: the issue depends on GSSAPI support, non-default SSH configuration, Kerberos indicator issuance, and a specific authentication control flow. The demonstrated impact is limited to availability loss in privileged authentication code, so Moderate is a better fit than Important, while remaining above Low because the out-of-bounds iteration is concrete rather than merely theoretical. Embargo: no Reason: The issue is configuration-dependent, the demonstrated impact is limited to availability, and the proposed fix is small and low-risk. A normal public fix cycle appears more appropriate than an embargo. Acknowledgement: Aisle Research Vulnerability Details: In sshgssapigetindicators(), the client->indicators array is reallocated from count to count + 1 elements and the newly added slot is immediately filled with a newly allocated string: c client->indicators = xrecallocarray(client->indicators, count, count + 1, sizeof(char)); if (client->indicators == NULL) { fatal("sshgssapigetindicators failed to allocate memory"); } client->indicators[count] = xmalloc(value.length + 1); memcpy(client->indicators[count], value.value, value.length); client->indicators[count][value.length] = '\0'; count++; In this source tree, xrecallocarray() wraps recallocarray(), which zeroes only the newly added region when the allocation grows. Here the newly added region is exactly one pointer wide, and that pointer is overwritten before count is incremented. As a result, once at least one authenticated indicator is collected, there is no guaranteed trailing NULL entry after the last valid element even though later code relies on sentinel termination. Two consumers iterate until NULL: c if (gssapiclient.indicators != NULL) { for (i = 0; gssapiclient.indicators[i] != NULL; i++) free(gssapiclient.indicators[i]); free(gssapiclient.indicators); } c for (i = 0; client->indicators[i] != NULL; i++) { ret = matchpatternlist(client->indicators[i], options.gssindicators, 1); ... } These loops may read beyond the allocated pointer array while searching for a terminator. On the sshgssapiuserok() failure cleanup path, that out-of-bounds read may also cause free() to be called on a non-owned pointer if adjacent heap contents are interpreted as another indicator entry. Based on the available evidence, the established security consequence is an availability problem; reliable confidentiality or integrity impact is not demonstrated. Steps to reproduce: 1. Build openssh-10.2p1-10.1.hum1 with GSSAPI support and memory sanitizers such as ASan/UBSan. 2. Run sshd with GSSAPIAuthentication yes. 3. Authenticate with a Kerberos ticket that contains at least one authenticated auth-indicators name attribute. 4. Trigger a sentinel-based consumer by either causing sshgssapiuserok() to return false, for example through principal-to-local-user authorization failure, or by configuring GSSAPIIndicators so sshgssapicheckindicators() iterates the list. 5. Observe a sanitizer-reported read past the end of the indicators pointer array; depending on heap layout, cleanup may also attempt to free a non-owned pointer. Mitigation: Disabling GSSAPIAuthentication removes the remotely reachable path. If GSSAPI authentication is required, avoiding GSSAPIIndicators removes one consumer but does not remove the cleanup path after sshgssapiuserok() failure; the safer temporary mitigation is to restrict or disable use of Kerberos tickets carrying authenticated auth-indicators values until a fix is deployed. Proposed Fix: Ensure sshgssapigetindicators() leaves a dedicated trailing NULL slot after indicator collection so all sentinel-based consumers remain within bounds. diff diff --git a/gss-serv.c b/gss-serv.c @@ done: - / slot [count] is zeroed by recallocarray, serves as NULL sentinel / + / + Ensure a trailing NULL sentinel for sentinel-based consumers. + / + if (client->indicators != NULL) + client->indicators = xrecallocarray(client->indicators, + count, count + 1, sizeof(char )); ------ This report was generated using AI technology. Always review AI-generated content prior to use

First published (updated )

Qualys Security Advisory

CVE-2025-26465: MitM attack against OpenSSH's VerifyHostKeyDNS-enabled client

CVE-2025-26466: DoS attack against OpenSSH's client and server

======================================================================== Contents ========================================================================

Summary Background Experiments Results MitM attack against OpenSSH's VerifyHostKeyDNS-enabled client DoS attack against OpenSSH's client and server (memory consumption) DoS attack against OpenSSH's client and server (CPU consumption) Proof of concept Acknowledgments Timeline

======================================================================== Summary ========================================================================

We discovered two vulnerabilities in OpenSSH:

- The OpenSSH client is vulnerable to an active machine-in-the-middle attack if the VerifyHostKeyDNS option is enabled (it is disabled by default): when a vulnerable client connects to a server, an active machine-in-the-middle can impersonate the server by completely bypassing the client's checks of the server's identity.

This attack against the OpenSSH client succeeds whether VerifyHostKeyDNS is "yes" or "ask" (it is "no" by default), without user interaction, and whether the impersonated server actually has an SSHFP resource record or not (an SSH fingerprint stored in DNS). This vulnerability was introduced in December 2014 (shortly before OpenSSH 6.8p1) by commit 5e39a49 ("Add RevokedHostKeys option for the client to allow text-file or KRL-based revocation of host keys"). For more information on VerifyHostKeyDNS:

https://man.openbsd.org/sshconfig#VerifyHostKeyDNS https://man.openbsd.org/ssh#VERIFYINGHOSTKEYS

Note: although VerifyHostKeyDNS is disabled by default, it was enabled by default on FreeBSD (for example) from September 2013 to March 2023; for more information:

https://cgit.freebsd.org/src/commit/?id=83c6a52 https://cgit.freebsd.org/src/commit/?id=41ff5ea

- The OpenSSH client and server are vulnerable to a pre-authentication denial-of-service attack: an asymmetric resource consumption of both memory and CPU. This vulnerability was introduced in August 2023 (shortly before OpenSSH 9.5p1) by commit dce6d80 ("Introduce a transport-level ping facility").

On the server side, this attack can be easily mitigated by mechanisms that are already built in OpenSSH: LoginGraceTime, MaxStartups, and more recently (OpenSSH 9.8p1 and newer) PerSourcePenalties; for more information:

https://man.openbsd.org/sshdconfig#LoginGraceTime https://man.openbsd.org/sshdconfig#MaxStartups https://man.openbsd.org/sshdconfig#PerSourcePenalties

======================================================================== Background ========================================================================

OpenSSH heavily uses the following idiom throughout its code base:

------------------------------------------------------------------------ 1387 int 1388 sshkeytobase64(const struct sshkey key, char b64p) 1389 { 1390 int r = SSHERRINTERNALERROR; .... 1398 if ((r = sshkeyputb(key, b)) != 0) 1399 goto out; 1400 if ((uu = sshbufdtob64string(b, 0)) == NULL) { 1401 r = SSHERRALLOCFAIL; 1402 goto out; 1403 } .... 1409 r = 0; 1410 out: .... 1413 return r; 1414 } ------------------------------------------------------------------------

- at line 1390, the return value r is safely initialized to a non-zero error code (to prevent sshkeytobase64() from mistakenly returning success, i.e. zero);

- at line 1398, if sshkeyputb() fails (if it returns a non-zero error code), then r is automatically set to this error code and immediately returned at line 1413;

- at line 1400, if sshbufdtob64string() fails (if it returns NULL), then r is manually reset to a non-zero error code at line 1401 and immediately returned at line 1413;

- if no error occurs at all in sshkeytobase64(), then at line 1409 r is set to zero (success) and eventually returned at line 1413.

This idiom left us pondering: what if the manual reset of the return value r at line 1401 were missing? Then sshkeytobase64() would mistakenly return zero (success) if sshbufdtob64string() fails at line 1400, because r was automatically set to zero at line 1398 (because, to reach line 1400, sshkeyputb() necessarily succeeded, at line 1398).

The consequences of such a mistake (a function that returns success although it clearly failed) would of course depend on the exact nature of the affected function and its callers; but we began to suspect that, in a large code base such as OpenSSH, some of the manual resets of the return values r were inevitably missing, and some of these mistakes may very well have security consequences.

Note: the same basic idea also underlies Kevin Backhouse's beautiful CVE-2023-2283, an authentication bypass in libssh; for more information:

https://securitylab.github.com/advisories/GHSL-2023-085libssh/ https://x.com/kevinbackhouse/status/1666459308941357056

======================================================================== Experiments ========================================================================

To confirm our suspicion, we adopted a dual strategy:

- we manually audited all of OpenSSH's functions that use "goto", for missing resets of their return value;

- we wrote a CodeQL query that automatically searches for functions that "goto out" without resetting their return value in the corresponding "if" code block.

Warning: our rudimentary CodeQL query (below) might hurt the eyes of experienced CodeQL programmers; if you, dear reader, are able to write a query that runs faster or produces less false positives, please post it to the public oss-security mailing list!

------------------------------------------------------------------------ / @kind problem @id cpp/test @problem.severity error /

import cpp

Stmt getRecChild(Stmt s) { result = s or result = getRecChild(s.getChildStmt()) }

ControlFlowNode getRecPredecessor(ControlFlowNode n) { result = n or result = getRecPredecessor(n.getAPredecessor()) }

from Function f, ReturnStmt r, LocalVariable v, IfStmt i, Stmt b where f.getBlock().getLastStmtIn() = r and exists(VariableAccess a | a.getTarget() = v and not a.isModified() and a.getEnclosingStmt() = getRecChild(r)) and v.getType() instanceof IntType and i.getEnclosingFunction() = f and i.getThen() = b and (b instanceof GotoStmt or b instanceof BlockStmt and ((BlockStmt)b).getLastStmtIn() instanceof GotoStmt) and not exists(Assignment a | a.getEnclosingFunction() = f and a.getLValue().toString() = v.getName() and a.getEnclosingStmt() = getRecChild(i)) and not exists(Assignment a | a.getEnclosingFunction() = f and a.getLValue().toString() = v.getName() and a.getEnclosingStmt() = getRecChild(b)) and exists(Assignment a | a.getEnclosingFunction() = f and a.getLValue().toString() = v.getName() and a.getLocation().toString() != v.getDefinitionLocation().toString() and a.getBasicBlock().getANode() = getRecPredecessor(i.getBasicBlock().getANode())) select f, b.getLocation().toString() ------------------------------------------------------------------------

======================================================================== Results ========================================================================

Against OpenSSH 9.9p1, this CodeQL query produces 50 results. Among these, on closer inspection, 37 are false positives:

------------------------------------------------------------------------ | addrmatchlist | addrmatch.c:91:5:91:17 | | hostkeysforeachfile | hostfile.c:806:5:806:13 | | hostkeysforeachfile | hostfile.c:819:25:823:4 | | hostkeysforeachfile | hostfile.c:832:26:837:5 | | hostkeysforeachfile | hostfile.c:856:36:860:3 | | hostkeysforeachfile | hostfile.c:874:55:876:4 | | hostkeysforeachfile | hostfile.c:884:5:884:13 | | hostkeysforeachfile | hostfile.c:895:5:895:13 | | sshkeyecdsafixupgroup | ssh-ecdsa.c:81:4:81:12 | | sshkeyecdsafixupgroup | ssh-ecdsa.c:88:3:88:11 | | sshkeyecdsafixupgroup | ssh-ecdsa.c:94:3:94:11 | | sshpacketreadpoll2 | packet.c:1696:5:1696:13 | | sshkeyloadprivatetype | authfile.c:133:3:133:11 | | kexexchangeidentification | kex.c:1332:32:1336:4 | | kexexchangeidentification | kex.c:1342:55:1345:4 | | kexexchangeidentification | kex.c:1358:25:1363:3 | | inputkexgeninit | kexgen.c:331:3:331:11 | | inputkexgenreply | kexgen.c:207:3:207:11 | | processconfiglinedepth | readconf.c:2444:14:2448:2 | | parseargs | sftp.c:1511:4:1511:21 | | deletefile | ssh-add.c:193:3:193:11 | | deletefile | ssh-add.c:199:64:203:2 | | addfile | ssh-add.c:401:3:401:11 | | addfile | ssh-add.c:405:60:410:2 | | addfile | ssh-add.c:412:43:417:2 | | addfile | ssh-add.c:420:47:424:2 | | addfile | ssh-add.c:425:50:429:2 | | addfile | ssh-add.c:434:50:438:2 | | sshreadbanner | sshapi.c:366:5:366:13 | | sshreadbanner | sshapi.c:370:5:370:13 | | sshcreatesocket | sshconnect.c:384:28:388:3 | | sshcreatesocket | sshconnect.c:389:20:392:3 | | sshcreatesocket | sshconnect.c:397:40:401:3 | | sshcreatesocket | sshconnect.c:404:47:408:3 | | sshcreatesocket | sshconnect.c:414:58:417:2 | | sshcreatesocket | sshconnect.c:418:66:421:2 | | identitysign | sshconnect2.c:1257:42:1265:3 | ------------------------------------------------------------------------

For example, the line 1696 in packet.c is obviously a false positive (although the "if" code block at lines 1695-1696 does not reset the return value r to a non-zero error code, our CodeQL query fails to notice that, to reach line 1695, r was necessarily set to a non-zero error code at lines 1691-1694):

------------------------------------------------------------------------ 1557 int 1558 sshpacketreadpoll2(struct ssh ssh, uchar typep, uint32t seqnrp) 1559 { .... 1691 if (!mac->etm && (r = maccheck(mac, state->pread.seqnr, 1692 sshbufptr(state->incomingpacket), 1693 sshbuflen(state->incomingpacket), 1694 sshbufptr(state->input), maclen)) != 0) { 1695 if (r != SSHERRMACINVALID) 1696 goto out; .... 1791 out: 1792 return r; 1793 } ------------------------------------------------------------------------

The remaining 13 results are all true positives (functions that return success although they clearly failed), but to the best of our knowledge, they have either no or minor security consequences:

------------------------------------------------------------------------ | sshkrlfromblob | krl.c:1061:38:1064:2 | | revokedcertsgenerate | krl.c:676:41:679:4 | | sshskloadresident | ssh-sk-client.c:440:48:443:3 | | sshskloadresident | ssh-sk-client.c:451:32:454:3 | | processextsessionbind | ssh-agent.c:1742:48:1745:2 | | parsekeyconstraintextension | ssh-agent.c:1209:22:1212:3 | | parsekeyconstraintextension | ssh-agent.c:1218:46:1221:4 | | parsekeyconstraintextension | ssh-agent.c:1235:23:1238:3 | | parsekeyconstraintextension | ssh-agent.c:1246:40:1249:4 | | inputuserauthpkok | sshconnect2.c:700:61:703:2 | | inputuserauthpkok | sshconnect2.c:708:27:713:2 | | inputuserauthpkok | sshconnect2.c:726:28:732:2 | | certfilterprincipals | sshsig.c:875:61:878:2 | ------------------------------------------------------------------------

For example, the missing resets of the return value r at lines 1209-1212, 1218-1221, 1235-1238, and 1246-1249 in ssh-agent.c could (in theory at least) result in parsekeyconstraintextension() returning success (because, to reach these lines, r was necessarily set to zero, at line 1187 for example), but without actually constraining the key that is being added to the ssh-agent:

------------------------------------------------------------------------ 1176 static int 1177 parsekeyconstraintextension(struct sshbuf m, char skproviderp, 1178 struct destconstraint dcsp, sizet ndcsp, int certonlyp, 1179 struct sshkey certs, sizet ncerts) 1180 { .... 1187 if ((r = sshbufgetcstring(m, &extname, NULL)) != 0) { 1188 errorfr(r, "parse constraint extension"); 1189 goto out; 1190 } .... 1209 if (dcsp != NULL) { 1210 errorf("%s already set", extname); 1211 goto out; 1212 } .... 1218 if (ndcsp >= AGENTMAXDESTCONSTRAINTS) { 1219 errorf("too many %s constraints", extname); 1220 goto out; 1221 } .... 1235 if (certs != NULL) { 1236 errorf("%s already set", extname); 1237 goto out; 1238 } .... 1246 if (ncerts >= AGENTMAXEXTCERTS) { 1247 errorf("too many %s constraints", extname); 1248 goto out; 1249 } .... 1265 out: .... 1268 return r; 1269 } ------------------------------------------------------------------------

======================================================================== MitM attack against OpenSSH's VerifyHostKeyDNS-enabled client ========================================================================

Our manual audit (of all the functions that use "goto") allowed us to verify that our CodeQL query does not produce false negatives (which would be worse than false positives), but it also allowed us to review code that is similar but not identical to the idiom presented in the "Background" section.

In OpenSSH's client, the following code, which checks the server's identity (the server's host key), naturally caught our attention:

------------------------------------------------------------------------ 93 static int 94 verifyhostkeycallback(struct sshkey hostkey, struct ssh ssh) 95 { ... 101 if (verifyhostkey(xxxhost, xxxhostaddr, hostkey, 102 xxxconninfo) == -1) 103 fatal("Host key verification failed."); 104 return 0; 105 } ------------------------------------------------------------------------ 1470 int 1471 verifyhostkey(char host, struct sockaddr hostaddr, struct sshkey hostkey, 1472 const struct sshconninfo cinfo) 1473 { .... 1538 if (options.verifyhostkeydns) { .... 1543 if ((r = sshkeyfromprivate(hostkey, &plain)) != 0) 1544 goto out; .... 1571 out: .... 1580 return r; 1581 } ------------------------------------------------------------------------

- in verifyhostkey() (when VerifyHostKeyDNS is enabled, at line 1538), if sshkeyfromprivate() returns any non-zero error code (at line 1543), then this error code is immediately returned to verifyhostkey()'s caller (at line 1580);

- unfortunately, in verifyhostkeycallback() (verifyhostkey()'s caller), only the return value -1 is treated as an error (at lines 101-103);

- any other return value (for example, -2) is ignored, and zero (success) is mistakenly returned to verifyhostkeycallback()'s caller (at line 104), as if no error had occurred, and without checking the server's host key at all.

The question, then, is: to impersonate this server, how can an active machine-in-the-middle force the client to return an error code other than -1 (SSHERRINTERNALERROR) at line 1543?

In theory, sshkeyfromprivate() can return many different error codes: -10 (SSHERRINVALIDARGUMENT), -14 (SSHERRKEYTYPEUNKNOWN), etc. In practice, however, the host-key structure that is copied at line 1543 was originally created by sshkeyfromb(), which would have fatally failed already if the server's host key were malformed.

The only error code that we were able to eventually force out of sshkeyfromprivate() is -2 (SSHERRALLOCFAIL), an out-of-memory error. (If you, dear reader, find another solution to this problem, please post it to the public oss-security mailing list!) Consequently, to carry out this machine-in-the-middle attack in practice (to successfully impersonate the real server), we must:

- make our fake server's host key as large as possible, to maximize our chances of exhausting the client's memory inside sshkeyfromprivate() at line 1543 -- but we are limited to ~256KB (PACKETMAXSIZE) because packet compression is not supported before the end of the initial key exchange (not even in the client);

- find a memory leak in the client's code (or at least an unlimited allocation of memory that is not freed before the end of the initial key exchange), to consume as much of the client's memory as possible before the call to sshkeyfromprivate() at line 1543.

And so began our quest for a pre-authentication memory leak in OpenSSH's client.

======================================================================== DoS attack against OpenSSH's client and server (memory consumption) ========================================================================

As yet another testimony to OpenSSH's code quality, we actually failed to find a pre-authentication memory leak. Instead, we found an unlimited allocation of memory that is not freed until the very end of the initial key exchange:

------------------------------------------------------------------------ 1796 sshpacketreadpollseqnr(struct ssh ssh, uchar typep, uint32t seqnrp) 1797 { .... 1863 case SSH2MSGPING: 1864 if ((r = sshpktgetstringdirect(ssh, &d, &len)) != 0) 1865 return r; 1866 DBG(debug("Received SSH2MSGPING len %zu", len)); 1867 if ((r = sshpktstart(ssh, SSH2MSGPONG)) != 0 || 1868 (r = sshpktputstring(ssh, d, len)) != 0 || 1869 (r = sshpktsend(ssh)) != 0) 1870 return r; 1871 break; ------------------------------------------------------------------------ 2827 sshpktsend(struct ssh ssh) 2828 { .... 2831 return sshpacketsend2(ssh); ------------------------------------------------------------------------ 1343 sshpacketsend2(struct ssh ssh) 1344 { .... 1360 if ((needrekey || state->rekeying) && !sshpackettypeiskex(type)) { .... 1368 p->payload = state->outgoingpacket; 1369 TAILQINSERTTAIL(&state->outgoing, p, next); 1370 state->outgoingpacket = sshbufnew(); .... 1381 return 0; 1382 } .... 1388 if ((r = sshpacketsend2wrapped(ssh)) != 0) ------------------------------------------------------------------------ 1163 sshpacketsend2wrapped(struct ssh ssh) 1164 { .... 1276 if ((r = sshbufreserve(state->output, 1277 sshbuflen(state->outgoingpacket) + authlen, &cp)) != 0) ------------------------------------------------------------------------

- every time a PING packet is received (at lines 1863-1864), a PONG packet is produced (at lines 1867-1868) and buffered (at line 1869), but not immediately sent out (because sshpacketwritewait() is not called explicitly);

- outside a key exchange (at line 1388), such a PONG packet (the "outgoingpacket") is simply appended to the "output" buffer (at lines 1276-1277): the memory allocated for these PONG packets is limited, because the number of PONG packets that can be buffered is limited by the maximum size of the "output" buffer, 128MB (SSHBUFSIZEMAX);

- on the other hand, during a key exchange (at lines 1360-1382), such a PONG packet (the current "outgoingpacket") is appended to a list of outgoing packets (at lines 1368-1369), and a new "outgoingpacket" is allocated (at line 1370): the memory allocated for these PONG packets is unlimited, because the number of PONG packets that can be buffered is unlimited.

This uncontrolled consumption of memory affects both the client and the server, and is also asymmetrical: for every 16B PING packet received, a PONG buffer of 256B (SSHBUFSIZEINIT) is allocated, and not freed until the very end of the key exchange. On the server side, this vulnerability is mitigated by the default LoginGraceTime: after 2 minutes, any memory- consuming connection is automatically killed by SIGALRM; however, since 100 concurrent connections are allowed by default, the MaxStartups and PerSourcePenalties options are also needed for a full mitigation.

On the client side, no mitigations exist for this vulnerability. In fact, it is ideal for our machine-in-the-middle attack: we use it to force the client to run out of memory inside sshkeyfromprivate(), and as soon as the initial key exchange with our fake server ends (and the real server is impersonated), all the allocated memory is freed, which guarantees that the client does not run out of memory later during the lifetime of its connection with our fake server.

We will demonstrate our machine-in-the-middle attack in the "Proof of concept" section below, but we must first discuss a second, unforeseen aspect of this vulnerability: an asymmetric resource consumption of CPU.

======================================================================== DoS attack against OpenSSH's client and server (CPU consumption) ========================================================================

As soon as the initial key exchange ends (at lines 1392-1416), every PONG packet that was buffered (not sent out) is removed from the list of outgoing packets (at lines 1395-1410) and re-buffered (at line 1413), by appending it to the "output" buffer (at lines 1276-1277). Unfortunately, this re-buffering has a quadratic time complexity (O(n^2)): for each and every PONG packet of 256B (SSHBUFSIZEINC, at line 352), a new "output" buffer is malloc()ated (at line 73), and the entire contents of the old buffer (the already re-buffered PONG packets) are copied into the new buffer (at line 78):

------------------------------------------------------------------------ 1343 sshpacketsend2(struct ssh ssh) 1344 { .... 1392 if (type == SSH2MSGNEWKEYS) { .... 1395 while ((p = TAILQFIRST(&state->outgoing))) { .... 1409 state->outgoingpacket = p->payload; 1410 TAILQREMOVE(&state->outgoing, p, next); .... 1413 if ((r = sshpacketsend2wrapped(ssh)) != 0) 1414 return r; 1415 } 1416 } ------------------------------------------------------------------------ 1163 sshpacketsend2wrapped(struct ssh ssh) 1164 { .... 1276 if ((r = sshbufreserve(state->output, 1277 sshbuflen(state->outgoingpacket) + authlen, &cp)) != 0) ------------------------------------------------------------------------ 372 sshbufreserve(struct sshbuf buf, sizet len, uchar dpp) 373 { ... 381 if ((r = sshbufallocate(buf, len)) != 0) ------------------------------------------------------------------------ 329 sshbufallocate(struct sshbuf buf, sizet len) 330 { ... 352 rlen = ROUNDUP(buf->alloc + need, SSHBUFSIZEINC); ... 357 if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) { ------------------------------------------------------------------------ 38 recallocarray(void ptr, sizet oldnmemb, sizet newnmemb, sizet size) 39 { .. 73 newptr = malloc(newsize); .. 78 memcpy(newptr, ptr, oldsize); ------------------------------------------------------------------------

For example, if we send 128MB (SSHBUFSIZEMAX) of PING packets (i.e., 128MB / 256B = 2^19 packets), then the re-buffering of the corresponding PONG packets would in theory end up copying 32TB in total (approximately 256B / 2 (2^19)^2 = 2^45 bytes).

In practice, if we send roughly 16MB of PING packets to the server (i.e., 16MB / 256B = 2^16 packets), and directly disconnect from the server, then we leave the server CPU spinning at 100% for 2 minutes (the default LoginGraceTime). Once again, because 100 concurrent connections are allowed by default, the MaxStartups and PerSourcePenalties options are also needed for a full mitigation.

======================================================================== Proof of concept ========================================================================

In the following example, "client" is running a VerifyHostKeyDNS-enabled OpenSSH client (version 9.6p1, from Ubuntu 24.04) whose available memory is limited to 256MB by an RLIMITDATA resource limit. "client" is trying to connect to the SSH server "real-server", but it is currently under an active machine-in-the-middle attack carried out by a "fake-server".

If this "fake-server" tries to impersonate the "real-server" without implementing the out-of-memory attack discussed in the three previous sections, then "client" immediately detects that "real-server"s host key has changed and aborts:

------------------------------------------------------------------------ client$ ulimit -S -a ... data seg size (kbytes, -d) 262144 ...

client$ /usr/bin/ssh -o VerifyHostKeyDNS=yes john@real-server @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! ... Host key for real-server has changed and you have requested strict checking. Host key verification failed. ------------------------------------------------------------------------

However, if "fake-server" does implement the out-of-memory attack discussed previously (by allocating a 1024-bit RSA key and a ~140KB certificate extension, plus ~234MB of PONG packets -- but many other combinations work), then the "client"s call to sshkeyfromprivate() returns SSHERRALLOCFAIL, and its checks of the "real-server"s host key are completely bypassed, thus allowing the "fake-server" to successfully impersonate the "real-server":

------------------------------------------------------------------------ fake-server# /usr/local/sbin/sshd -o KexAlgorithms=curve25519-sha256 -h /root/exploit2349254741408771024ssh-rsa ------------------------------------------------------------------------ client$ /usr/bin/ssh -o VerifyHostKeyDNS=yes john@real-server john@real-server's password: fake-server$ ------------------------------------------------------------------------

Side note: this password prompt takes longer than usual to be displayed, because of the quadratic re-buffering of the numerous PONG packets.

======================================================================== Acknowledgments ========================================================================

We thank the OpenSSH developers (in particular Markus Friedl, Damien Miller, and Theo de Raadt) for their outstanding work on this release and on OpenSSH in general. We also thank the members of distros@openwall (in particular Salvatore Bonaccorso, Marco Benatto, and Solar Designer).

Finally, we thank Bad Sector Labs for their kind words about our work and for their excellent "Last Week in Security" blog posts:

https://blog.badsectorlabs.com/last-week-in-security-lwis-2024-11-25.html

======================================================================== Timeline ========================================================================

2025-01-31: Advisory and proofs of concepts sent to openssh@openssh.

2025-02-10: Advisory and patches sent to distros@openwall.

2025-02-18: Coordinated release.

Dear Alexander,

Thanks for notifying! Yes, GSSAPIKeyExchange should be mentioned instead of GSSAPIAuthentication.

On Sat, Mar 14, 2026 at 8:50 PM Solar Designer <solar () openwall com> wrote: On Thu, Mar 12, 2026 at 02:03:23PM -0400, Marc Deslauriers wrote: Jeremy Brown discovered a security issue in the GSSAPI Key Exchange patch a lot of distros carry on top of the OpenSSH package. I'm Bcc'ing Jeremy on this. Thank you for discovering and reporting the issue, Jeremy! Unfortunately, there seems to be quite a few different versions of this patch being used, but a lot of them share the same core issue. Different compiler options also result in different outcomes, so the severity of this issue varies.

We have assigned CVE-2026-3497 to this issue.

Attached is the full pdf from the reporter, along with the patch we used in Ubuntu. I suggest reading the full pdf, but I have extracted some of the most important excerpts from it:

"The patch contains a code defect where sshpktdisconnect() (a non-terminating function that queues a disconnect message and returns) is used where sshpacketdisconnect()(which terminates the process) was intended. This causes the default: error-handling case in the GSSAPI KEX server loop to fall through into code that reads an uninitialized stack variable (recvtok), sends its contents to the privileged monitor process via IPC, and then passes it to gssreleasebuffer() which may call free() on a garbage pointer."

"Bug: Non-terminating error handler (sshpktdisconnect) in GSSAPI KEX server code allows fallthrough to uninitialized variable use

- Impact: Pre-auth uninitialized pointer dereference (CWE-824, CWE-908); confirmed heap corruption via free() on uninitialized pointer (SIGABRT on x8664); privsep boundary violation (up to 127KB of heap data to root monitor via IPC); SIGSEGV (signal 11) and SIGABRT (signal 6) on x8664 with 90-second SSH lockout; 100% reliable child process crash

- Trigger: Single crafted SSH packet (~300 bytes), no authentication or credentials needed

- Potentially Affected: Ubuntu/Debian OpenSSH servers with GSSAPIKeyExchange yes Red Hat has now acknowledged that RHEL 8, 9, 10 are also affected (but 6 and 7 are not):

https://access.redhat.com/security/cve/cve-2026-3497

They suggest setting "GSSAPIAuthentication no" to mitigate this, which I find puzzling. Per the brief discussion we had on the distros list pre-disclosure, it appeared that GSSAPIKeyExchange is the option, and moreover it was said that GSSAPIKeyExchange could conceivably be used without GSSAPIAuthentication. So which of these two options is/are actually responsible for exposing the vulnerability? Does it maybe vary by patch revision (Debian vs. Red Hat) or (more likely?) is this just an error in the current Red Hat statement?

Also, the statement is worded such that it's implied the default configuration is affected and the mitigation needs to be applied, but prior understanding was that these features are disabled by default. - Potential Fix: Replace sshpktdisconnect() with sshpacketdisconnect() at the 3 server-side call sites in kexgsss.c"

"The uninitialized recvtok contains different stack residue depending on compiler, optimization level, and flags."

"Different compilers produce fundamentally different residue. Clang -O0 leaves 0xfffbe600 with length 4. GCC -O2 -fno-stack-protector leaves a valid heap address with length 127344. The 8-build matrix shows that recvtok.value ranges from NULL to stack addresses to heap addresses to unmapped addresses." Thank you for bringing this to oss-security, Marc!

Alexander -- Dmitry Belyavskiy

On Thu, Mar 12, 2026 at 02:03:23PM -0400, Marc Deslauriers wrote: Jeremy Brown discovered a security issue in the GSSAPI Key Exchange patch a lot of distros carry on top of the OpenSSH package. I'm Bcc'ing Jeremy on this. Thank you for discovering and reporting the issue, Jeremy! Unfortunately, there seems to be quite a few different versions of this patch being used, but a lot of them share the same core issue. Different compiler options also result in different outcomes, so the severity of this issue varies.

We have assigned CVE-2026-3497 to this issue.

Attached is the full pdf from the reporter, along with the patch we used in Ubuntu. I suggest reading the full pdf, but I have extracted some of the most important excerpts from it:

"The patch contains a code defect where sshpktdisconnect() (a non-terminating function that queues a disconnect message and returns) is used where sshpacketdisconnect()(which terminates the process) was intended. This causes the default: error-handling case in the GSSAPI KEX server loop to fall through into code that reads an uninitialized stack variable (recvtok), sends its contents to the privileged monitor process via IPC, and then passes it to gssreleasebuffer() which may call free() on a garbage pointer."

"Bug: Non-terminating error handler (sshpktdisconnect) in GSSAPI KEX server code allows fallthrough to uninitialized variable use

- Impact: Pre-auth uninitialized pointer dereference (CWE-824, CWE-908); confirmed heap corruption via free() on uninitialized pointer (SIGABRT on x8664); privsep boundary violation (up to 127KB of heap data to root monitor via IPC); SIGSEGV (signal 11) and SIGABRT (signal 6) on x8664 with 90-second SSH lockout; 100% reliable child process crash

- Trigger: Single crafted SSH packet (~300 bytes), no authentication or credentials needed

- Potentially Affected: Ubuntu/Debian OpenSSH servers with GSSAPIKeyExchange yes Red Hat has now acknowledged that RHEL 8, 9, 10 are also affected (but 6 and 7 are not):

https://access.redhat.com/security/cve/cve-2026-3497

They suggest setting "GSSAPIAuthentication no" to mitigate this, which I find puzzling. Per the brief discussion we had on the distros list pre-disclosure, it appeared that GSSAPIKeyExchange is the option, and moreover it was said that GSSAPIKeyExchange could conceivably be used without GSSAPIAuthentication. So which of these two options is/are actually responsible for exposing the vulnerability? Does it maybe vary by patch revision (Debian vs. Red Hat) or (more likely?) is this just an error in the current Red Hat statement?

Also, the statement is worded such that it's implied the default configuration is affected and the mitigation needs to be applied, but prior understanding was that these features are disabled by default. - Potential Fix: Replace sshpktdisconnect() with sshpacketdisconnect() at the 3 server-side call sites in kexgsss.c"

"The uninitialized recvtok contains different stack residue depending on compiler, optimization level, and flags."

"Different compilers produce fundamentally different residue. Clang -O0 leaves 0xfffbe600 with length 4. GCC -O2 -fno-stack-protector leaves a valid heap address with length 127344. The 8-build matrix shows that recvtok.value ranges from NULL to stack addresses to heap addresses to unmapped addresses." Thank you for bringing this to oss-security, Marc!

Alexander

Dear colleagues,

Thanks for sharing your findings! Can we somehow establish some better coordination in case of widely used downstream patches, especially for such an important, ubiquitous and heavily patched component as OpenSSH?

On Thu, Mar 12, 2026 at 7:09 PM Marc Deslauriers < marc.deslauriers () canonical com> wrote: Hello,

Jeremy Brown discovered a security issue in the GSSAPI Key Exchange patch a lot of distros carry on top of the OpenSSH package.

Unfortunately, there seems to be quite a few different versions of this patch being used, but a lot of them share the same core issue. Different compiler options also result in different outcomes, so the severity of this issue varies.

We have assigned CVE-2026-3497 to this issue.

Attached is the full pdf from the reporter, along with the patch we used in Ubuntu. I suggest reading the full pdf, but I have extracted some of the most important excerpts from it:

"The patch contains a code defect where sshpktdisconnect() (a non-terminating function that queues a disconnect message and returns) is used where sshpacketdisconnect()(which terminates the process) was intended. This causes the default: error-handling case in the GSSAPI KEX server loop to fall through into code that reads an uninitialized stack variable (recvtok), sends its contents to the privileged monitor process via IPC, and then passes it to gssreleasebuffer() which may call free() on a garbage pointer."

"Bug: Non-terminating error handler (sshpktdisconnect) in GSSAPI KEX server code allows fallthrough to uninitialized variable use

- Impact: Pre-auth uninitialized pointer dereference (CWE-824, CWE-908); confirmed heap corruption via free() on uninitialized pointer (SIGABRT on x8664); privsep boundary violation (up to 127KB of heap data to root monitor via IPC); SIGSEGV (signal 11) and SIGABRT (signal 6) on x8664 with 90-second SSH lockout; 100% reliable child process crash

- Trigger: Single crafted SSH packet (~300 bytes), no authentication or credentials needed

- Potentially Affected: Ubuntu/Debian OpenSSH servers with GSSAPIKeyExchange yes

- Potential Fix: Replace sshpktdisconnect() with sshpacketdisconnect() at the 3 server-side call sites in kexgsss.c"

"The uninitialized recvtok contains different stack residue depending on compiler, optimization level, and flags."

"Different compilers produce fundamentally different residue. Clang -O0 leaves 0xfffbe600 with length 4. GCC -O2 -fno-stack-protector leaves a valid heap address with length 127344. The 8-build matrix shows that recvtok.value ranges from NULL to stack addresses to heap addresses to unmapped addresses."

Thanks,

Marc. -- Marc Deslauriers Ubuntu Security Engineer | http://www.ubuntu.com/ Canonical Ltd. | http://www.canonical.com/ -- Dmitry Belyavskiy

Dear Alexander,

On Wed, Mar 18, 2026 at 3:09 PM Solar Designer <solar () openwall com> wrote: Hi Dmitry,

On Wed, Mar 18, 2026 at 09:14:31AM +0100, Dmitry Belyavskiy wrote: Can we somehow establish some better coordination in case of widely used downstream patches, especially for such an important, ubiquitous and heavily patched component as OpenSSH? This was brought to the distros list on March 5. On March 6, I wrote:

"Looks like Red Hat packages are also affected. In particular, I looked at openssh-8.0p1-gssapi-keyex.patch from RHEL 9."

so it's not like Red Hat could assume this was limited to Debian/Ubuntu.

I now recall that something similar happened on a previous occasion, where you were not aware of a relevant issue until public disclosure.

So we seem to have a question to the Red Hat security team here - are you going to be informing your package maintainers of embargoed issues (which I consider fitting the need-to-know condition of the distros list), or are you deliberately handling them differently, or neither? Thanks, I will investigate it. I suppose this could reasonably vary by package - security updates to be prepared by security team vs. by package maintainer.

Should I be taking care of notifying Dmitry for OpenSSH specifically, where we know that he's eager to prepare for these disclosures but is often left out of the loop? IIRC, from the previous occasion I actually planned to start doing that, but I forgot, I'm sorry. No problem, thank you! I think OpenSSH, being heavily patched, deserves some special procedure, and can invite you to the discussion, if you are interested.

Meanwhile, I see the Mitigation section in https://access.redhat.com/security/cve/cve-2026-3497 has been updated to correctly refer to GSSAPI key exchange rather than authentication, but it still seems to imply the default configuration is affected - which I think it is not, or is it? I don't think so; I'll double-check. I wasn't too concerned about this issue for the Rocky Linux SIG/Security package that I maintain because we build it without Kerberos and GSSAPI support since March 2024. The patch is still applied, but the GSSAPIKeyExchange setting does not exist for real (is silently ignored via an extra patch for compatibility with FIPS configs that disable it), so it can't possibly be enabled there. Ditto in CIQ's RLC Pro Hardened. Thank you very much!

-- Dmitry Belyavskiy

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203