It simply rejects unbounded PKCS#1 keys, that are deviant because they
should be DER-encoded. This allowed removal of a potential OOB read
culprit function `asn1_new_from_bytes`.
Also:
- included is a commit fixing a wrong EOC length bug in ASN.1 parser.
Follow-up to 7391a24286#2287
Reported-by: Syed Abdul Khaliq
Fixes#2665Closes#2668
`ssh2_userauth_publickey()` returns the `userauth_key_sign_algs()` error
without freeing `session->userauth_pblc_method`, which happens when the
server's `server-sig-algs` has none of the RSA algorithms we can upgrade
an RSA key to. The next call sees the non-NULL method and skips parsing
it from the new key, so when iterating agent identities an ed25519 key
is offered as `ssh-rsa` and every later key fails with "No signing
signature matched" (the fromfile/frommemory/sk paths overwrite and leak
it instead). Free it there like the other error returns in the function
do; the new `test_simple` case fails without the fix.
Closes#2673
kex_mlkem_nistp() allocates shared_secret on the heap, hashes it into K,
zeroes it, and frees it only at the clean_free label. The NEWKEYS send
returns EAGAIN with a bare `return rc` that skips clean_free, so in
non-blocking mode a stalled write there leaks the buffer once per hybrid
handshake; the resumed call re-enters at ssh2_NB_state_sent and never
allocates (or frees) it again. Route that EAGAIN return through
clean_free, matching the kex_finish EAGAIN path just below it. The
buffer is already consumed by that point, and exchange_state is
untouched so the resume still works.
Only kex_mlkem_nistp has this: the sibling kex_mlkem768x25519_sha256()
keeps its shared secret on the stack and the other kex methods hold
their buffers in exchange_state, freed by their cleanup.
Closes#2670
userauth_list() keeps the server's USERAUTH_FAILURE/BANNER response in
session->userauth_list_data. Five of its error returns free that buffer
before returning NULL (the data_len < 5 check, the three banner checks,
and the SUCCESS case), but the oversize-methods check does not. When a
server sends a methods length field that is >= the bytes actually
present, the function returns at the LIBSSH2_ERROR_OUT_OF_BOUNDARY
branch with the response packet still allocated, so it leaks (the
pointer is only reclaimed at session teardown, or overwritten and leaked
again if the caller retries).
The patch adds the SSH2_SAFEFREE already used by the adjacent data_len <
5 branch, so the last error path frees what the function owns. Trigger
is a server response whose methods length field exceeds the packet body.
Closes#2667
Fix remote compression flag check. Tested all local/remote
configurations with zlib, zlib@openssh.com and none ordering.
Follow-up to ad88325b3f
Follow-up to 42fec44c8aFixes#2264Closes#2669
Avoid using names for internal function pointers that happen to be POSIX
or other system functions. To improve readability and to avoid issues
when the standard symbols are macros or need to be redefined. E.g. as
`connect()` is in OS/400 builds.
Also:
- agent: drop intermediate, single-use typedefs, while there.
Reported-by: Patrick Monnerat
Bug: https://github.com/libssh2/libssh2/pull/2668#issue-5849108984Closes#2674
MidnightBSD's `mport install` is prone to return a non-zero exit code on
success (hence the need to use `|| true`; also unrelated to the `grep`
filtering, needed to avoid log noise for no built-in option for this).
Work around by verifying success explicitly, so that a truly failed
install command fails in the 'install prereqs' step, instead of raising
a confusing error (e.g. "cmake: not found") in the step after.
Follow-up to b7f4e55c8f#2120
Follow-up to e4f700e934#1948Closes#2663
Prior to this patch, the documentation stated that the `.text` buffers
filled by the API caller are "free(3)d" by libssh2, referring to the
default system deallocator. In reality however, libssh2 was using
`SSH2_FREE*()` to free these buffers, respecting custom allocators, if
set by the user via `libssh2_session_init_ex()`.
libssh2 behaved this way since the initial implementation committed in
da653774aa 2005-06-11, v0.11
Later, examples were added that use `strdup()` to allocate, which was
(and is) accurate because they do not use a custom allocator:
9a1ce06e31 2007-07-22
Then, when the documentation landed, it incorrectly documented
`free(3)`, perhaps basing it on the examples added earlier:
d4aa801f6f 2008-03-08
I assume that callers overriding the custom allocators are more likely
to have fixed their code to adhere with how libssh2 actually behaved,
rather than believing the documentation and live with an allocator
mismatch for all these years.
Therefore I think this is a documentation error, and this patch
addresses this.
Reported-by: Makise Kurisu
Fixes GHSA-p6p8-66vc-3f24
Reverts ce631aafe1#2659
Follow-up to d4aa801f6f
Follow-up to da653774aaCloses#2661
`LIBSSH2_USERAUTH_KBDINT_PROMPT` and `LIBSSH2_USERAUTH_KBDINT_RESPONSE`
have a `text` pointer which require using the default system allocator
by the API user, as documented in
`docs/libssh2_userauth_keyboard_interactive_ex.md`.
To align with `libssh2_userauth_publickey_sk()` and best-practices, add
FIXMEs to replace this requirement to use `LIBSSH2_ALLOC_FUNC` for the
custom allocator set via `libssh2_session_init_ex()` (or, if not set use
the default system allocator.)
The above fix requires an ABI break, or a replacement parallel API that
fixes this issue.
Reported-by: Makise Kurisu
Ref: GHSA-p6p8-66vc-3f24
Closes#2659
`SSH2_ALLOC()` (or formerly `LIBSSH2_ALLOC()`) is an internal macro, not
available for API users. Mention `LIBSSH2_ALLOC_FUNC` instead to refer
to the custom allocator set via `libssh2_session_init_ex()`. If no
custom allocator was set, it means the default system allocator.
Follow-up to ed439a29bb#698Closes#2660
When sending the open confirmation in `packet_x11_open()` or
`packet_queue_listener()` returns EAGAIN, the resumed call loses the
channel it just allocated: the x11 path never saves it in
`x11open_state->channel`, so it links a NULL channel and crashes, and
the listener path clears `listen_state->channel` on every pass, so the
server gets a confirmation for a channel that is never queued and leaks.
This keeps the pointer in the state struct the same way
`packet_authagent_open()` already does, which I checked with a send
callback that returns EAGAIN once (ASan SEGV in `ssh2_list_add()`
before, and the channel reaches the x11 callback and the accept queue
after). I also looked at #1427, which touched the same EAGAIN return,
and this is the gap behind the question raised there about the channel
still being usable when the function is re-entered.
Reported-by: Yin Luming
Ref: https://github.com/LuMingYinDetect/libssh_defects/blob/main/libssh_1.pngFixes#1427Closes#2656
To 1^20, which ensures the function does complete eventually.
The limit mirrors the one introduced in OpenSSH 10.6. (Release Notes
say 1M, the actual patch uses 1^20; we go with the latter.)
Refs:
https://www.openssh.org/txt/release-10.6f938c78d49
OpenBSD-Commit-ID: 843ff37b066b2879f4579a784e42a3c9d22b2ddc
Reported-by: QcN3ep
Fixes GHSA-vc97-vgww-qggv
Closes#2657
- kex: clear `k_value` buffer in `kex_*_state_cleanup()` functions.
Reported-by: Ravikanth
Fixes#2653
- kex: clear shared secret buffer after use in
`kex_mlkem768x25519_sha256()`.
Reported-by: Ravikanth
Fixes#2654
Follow-up to 3ba252e2a6#1644
- kex: clear shared secret buffer after use in `kex_mlkem_nistp()`.
Follow-up to 3ba252e2a6#1644Closes#2655
ssh2_dh_secret() in the Windows CNG backend uses the server's
Diffie-Hellman public value f without the range check the other four
backends apply through ssh2_dh_validate(), so a malicious server can
send f of 0, 1 or p-1 and push the shared secret into a trivial subgroup
that openssl, libgcrypt, mbedtls and os400qc3 all reject with
LIBSSH2_ERROR_PROTO. This adds a WinCNG ssh2_dh_validate() that enforces
1 < f < p-1 and the same minimum-set-bits check on the big-endian value,
and calls it at the top of ssh2_dh_secret() like the other backends do.
The shared test_ssh2_dh_validate() in tests/test_simple.c was stubbed
out on WinCNG only because the function did not exist, so it now runs
against the real implementation too.
Reported-by: yaotushaozhu on github
Ref: GHSA-m9rg-h9jv-9gvh
Follow-up to fcf830788d#2239Closes#2645
All public API functions in `src/agent.c` that take a `LIBSSH2_AGENT *`
were missing a NULL-pointer check at entry.
A caller that passes NULL (for example, after `libssh2_agent_init()`
returns NULL due to OOM) immediately dereferences the null pointer and
crashes. Every other source file in this tree already has the pattern:
`channel.c`, `sftp.c`, `session.c`, `userauth.c`, and `knownhost.c` all
guard their primary handle before touching any field. `agent.c` was the
only outlier.
Add `if(!agent)` guards consistent with the style used in the rest of
the codebase.
Closes#2635
In `kex_mlkem768x25519_sha256` the x25519 half of the hybrid shared
secret is written into a fixed 64-byte buffer with `ssh2_bn_to_bin`,
which emits only the significant bytes and drops any leading zeros. When
the x25519 secret has a leading zero byte (about 1 in 256 handshakes,
and it comes from the server's ephemeral key) the write is short, so the
tail of the buffer is hashed while still uninitialized and the value
sits left-shifted instead of filling the fixed 32-byte field. That means
an uninitialized stack read into the exchange hash and a wrong K, which
fails the signature check for mlkem768x25519-sha256, the first KEX we
offer. This zero-fills the 32-byte field and writes the value
right-aligned so it is always the full fixed-width secret. The sibling
`kex_mlkem_nistp` sizes its heap buffer to the bignum length, so it has
no uninitialized read.
Bug possibly related: https://github.com/libssh2/libssh2/pull/1644#issuecomment-4761871304
Follow-up to 3ba252e2a6#1644Closes#2633
`wcng_privatekey_from_point()` sizes the ECC private-key blob with
`sizeof(BCRYPT_ECCPRIVATE_BLOB)`, but that name is the wide blob-type
string `L"ECCPRIVATEBLOB"`, not a struct, so the `sizeof` measures the
string (30 bytes on Windows) rather than the 8-byte `BCRYPT_ECCKEY_BLOB`
header. The three `memcpy` offsets right below it, and the public-key
sibling `wcng_publickey_from_point()`, already use
`sizeof(BCRYPT_ECCKEY_BLOB)`, so the buffer ends up oversized and its
uninitialized tail is handed to `BCryptImportKeyPair()` with an inflated
`cbInput`. This is on the path that loads an OpenSSH-format ECDSA
private key through
`libssh2_userauth_publickey_frommemory()`/`fromfile()` on the CNG
backend.
Using `sizeof(BCRYPT_ECCKEY_BLOB)` lines the allocation up with the
header the copies actually write from and with the length passed to
`BCryptImportKeyPair()`. Keeping it in step with the public-key path
means the private blob is described to CNG with exactly the bytes it
holds.
Follow-up to 3e72343737#1315Closes#2631
Also:
- enable MSYS2 cache for both curl tests.
- inherit pre-installed MSYS2 in the ARM64 curl job.
- bump integration tests from mingw64 to ucrt64 env.
- switch deprecated mingw64 jobs to ucrt64/clang64.
(Keep one mingw64 and one mingw32 job.)
- silence mingw64/mingw32 deprecation warning.
Closes#2638
For slightly better performance, and better efficiency.
Saving over 1 minute. The clang-tidy build is 2x fast.
Also:
- GHA/windows: fix `windres` command-line for non-intel machines.
Closes#2637
- update `docker/build-push-action` from 7.3.0 to 7.4.0
- update `github/codeql-action/analyze` from 4.37.8 to 4.38.2
- update `github/codeql-action/init` from 4.37.8 to 4.38.2
Closes#2634
To help debugging issues.
Also:
- bump sshd LogLevel to DEBUG1, for essential details.
- bump command output buffer in `run_command_varg()`
- drop redundant condition in `run_command_varg()`
- replace `BUFSIZ` with literal sizes in `run_command_varg()`.
After this patch it's showing this in the CI log for the libgcrypt
fallout that previously needed a long time to diagnose:
```
61: ---- sshd log ----
61: No matching private key for certificate: /etc/ssh/ssh_host_mldsa44-ed25519_key-cert.pub
61: Server listening on 0.0.0.0 port 22.
61: Server listening on :: port 22.
61: debug1: sshd-session version OpenSSH_10.5, OpenSSL 3.6.3 9 Jun 2026
61: Connection from 172.17.0.1 port 51878 on 172.17.0.2 port 22 rdomain ""
61: debug1: sshd-auth version OpenSSH_10.5, OpenSSL 3.6.3 9 Jun 2026 [preauth]
61: debug1: list_hostkey_types: rsa-sha2-512,rsa-sha2-256,ssh-rsa,rsa-sha2-512-cert-v01@openssh.com,
rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,
ecdsa-sha2-nistp256-cert-v01@openssh.com,ssh-ed25519,ssh-ed25519-cert-v01@openssh.com [preauth]
61: debug1: Local version string SSH-2.0-OpenSSH_10.5p1 Debian-1 [preauth]
61: debug1: Remote protocol version 2.0, remote software version libssh2_1.11.2_DEV [preauth]
61: debug1: compat_banner: no match: libssh2_1.11.2_DEV [preauth]
61: debug1: SSH2_MSG_KEXINIT sent [preauth]
61: debug1: SSH2_MSG_KEXINIT received [preauth]
61: debug1: kex: algorithm: (no match) [preauth]
61: Unable to negotiate with 172.17.0.1 port 51878: no matching key exchange method found.
Their offer: diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,
diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,
diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,ext-info-c,
kex-strict-c-v00@openssh.com [preauth]
[...]
61: ------------------
```
Follow-up to 1d0b86ac84#2484
Follow-up to 8be5716437#2174
Follow-up to 51f6259d18#1720Closes#2621