Visitar URL original
kex: skip signaling-type kex entries when negotiating by vszakats · Pull Request #2199 · libssh2/libssh2 · GitHub
Skip to content

kex: skip signaling-type kex entries when negotiating - #2199

Open
vszakats wants to merge 8 commits into
libssh2:masterfrom
vszakats:kexfilt
Open

vszakats wants to merge 8 commits into
libssh2:masterfrom
vszakats:kexfilt

Conversation

@vszakats

@vszakats vszakats commented Jul 3, 2026 •

Copy link
Copy Markdown
Member

Reported-by: Haruki Oyama (Waseda University)

Follow-up to 00e2a07 #1327 #1326
Follow-up to 64a555d #626


https://github.com/libssh2/libssh2/pull/2199/files?w=1

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens libssh2’s key-exchange (KEX) negotiation to avoid “signalling-only” KEX algorithm names (e.g., ext-info-c, kex-strict-c-v00@openssh.com) being treated as a negotiated KEX method, and adds a defensive failure if such a method is ever selected.

Changes:

  • Skip kex_method entries that have no exchange_keys implementation when negotiating a KEX method (default method list path).
  • Add an explicit runtime error if the negotiated session->kex has no KEX handler, preventing a silent “no-op” exchange.
  • Document the intent in-code (why these entries must not be negotiated).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/kex.c Resolved
@vszakats

vszakats commented Jul 4, 2026 •

Copy link
Copy Markdown
Member Author

The initial patch seems to trip CIFuzz, pushing it to a timeout,
perhaps due to an endless loop introduced?:
https://github.com/libssh2/libssh2/actions/runs/28673642941/job/85122565390?pr=2199

Copilot AI review requested due to automatic review settings July 22, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/kex.c:3350

  • The new exchange_keys filter correctly avoids selecting signalling-only KEX entries when session->kex_prefs is NULL, but the session->kex_prefs negotiation path above can still select ext-info-c / kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this file). Since libssh2_session_method_pref() prepends these extensions to user KEX prefs, users who set KEX prefs can still end up negotiating a method with no handler and now hit the new runtime error in ssh2_kex_exchange(). The prefs-driven loop should also skip methods with exchange_keys == NULL so it continues searching for a real KEX implementation.
    }

    while(*kexp && (*kexp)->name) {
        /* Skip signalling-only entries (ext-info-c,
         * kex-strict-c-v00@openssh.com) that carry no real KEX implementation

vszakats added a commit to vszakats/libssh2 that referenced this pull request Jul 22, 2026
"The signalling-only KEX entries are skipped in the default
`kex_methods` loop, but the `session->kex_prefs` negotiation path can
still select a method with `exchange_keys == NULL` (e.g., if a server
incorrectly advertises `ext-info-c` / `kex-strict-c-v00@openssh.com`).
With the new hard-failure in `ssh2_kex_exchange()`, this would cause an
avoidable connection failure instead of falling through to the next real
KEX method. Consider adding the same `exchange_keys` filter to the
`session->kex_prefs` loop before calling `kex_agree_hostkey()`/setting
`session->kex`."

libssh2#2199 (comment)

"The new exchange_keys filter correctly avoids selecting signalling-only
KEX entries when session->kex_prefs is NULL, but the session->kex_prefs
negotiation path above can still select ext-info-c /
kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this
file). Since libssh2_session_method_pref() prepends these extensions to
user KEX prefs, users who set KEX prefs can still end up negotiating a
method with no handler and now hit the new runtime error in
ssh2_kex_exchange(). The prefs-driven loop should also skip methods with
exchange_keys == NULL so it continues searching for a real KEX
implementation."
Copilot AI review requested due to automatic review settings July 22, 2026 20:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

vszakats added a commit to vszakats/libssh2 that referenced this pull request Jul 22, 2026
"The signalling-only KEX entries are skipped in the default
`kex_methods` loop, but the `session->kex_prefs` negotiation path can
still select a method with `exchange_keys == NULL` (e.g., if a server
incorrectly advertises `ext-info-c` / `kex-strict-c-v00@openssh.com`).
With the new hard-failure in `ssh2_kex_exchange()`, this would cause an
avoidable connection failure instead of falling through to the next real
KEX method. Consider adding the same `exchange_keys` filter to the
`session->kex_prefs` loop before calling `kex_agree_hostkey()`/setting
`session->kex`."

libssh2#2199 (comment)

"The new exchange_keys filter correctly avoids selecting signalling-only
KEX entries when session->kex_prefs is NULL, but the session->kex_prefs
negotiation path above can still select ext-info-c /
kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this
file). Since libssh2_session_method_pref() prepends these extensions to
user KEX prefs, users who set KEX prefs can still end up negotiating a
method with no handler and now hit the new runtime error in
ssh2_kex_exchange(). The prefs-driven loop should also skip methods with
exchange_keys == NULL so it continues searching for a real KEX
implementation."
Copilot AI review requested due to automatic review settings July 22, 2026 21:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

vszakats added a commit to vszakats/libssh2 that referenced this pull request Jul 23, 2026
"The signalling-only KEX entries are skipped in the default
`kex_methods` loop, but the `session->kex_prefs` negotiation path can
still select a method with `exchange_keys == NULL` (e.g., if a server
incorrectly advertises `ext-info-c` / `kex-strict-c-v00@openssh.com`).
With the new hard-failure in `ssh2_kex_exchange()`, this would cause an
avoidable connection failure instead of falling through to the next real
KEX method. Consider adding the same `exchange_keys` filter to the
`session->kex_prefs` loop before calling `kex_agree_hostkey()`/setting
`session->kex`."

libssh2#2199 (comment)

"The new exchange_keys filter correctly avoids selecting signalling-only
KEX entries when session->kex_prefs is NULL, but the session->kex_prefs
negotiation path above can still select ext-info-c /
kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this
file). Since libssh2_session_method_pref() prepends these extensions to
user KEX prefs, users who set KEX prefs can still end up negotiating a
method with no handler and now hit the new runtime error in
ssh2_kex_exchange(). The prefs-driven loop should also skip methods with
exchange_keys == NULL so it continues searching for a real KEX
implementation."
Copilot AI review requested due to automatic review settings July 23, 2026 00:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

src/kex.c:3377

  • More unconditional printf() debug output was left in the agreed-method path; this should not ship in libssh2. If you need diagnostics, use the existing ssh2_deb/LIBSSH2_TRACE_KEX mechanism instead.
                if(kex_agree_hostkey(session, (*kexp)->flags, hostkey,
                                     hostkey_len) == 0) {
                    printf("AGREE-2\n");
                    session->kex = *kexp;
                    if(session->burn_optimistic_kexinit && kex == s)
                        /* Server sent an optimistic packet, and client agrees
                         * with preference cancel burning the first KEX_INIT
                         * packet that comes in */
                        session->burn_optimistic_kexinit = 0;

                    printf("RETURN 0\n");
                    return 0;

src/kex.c:3384

  • Remove the remaining unconditional printf() at the end of negotiation; returning -1 is sufficient here and avoids unexpected output during handshake.
    printf("END=-1\n");
    return -1;

Comment thread src/kex.c Outdated
Comment on lines +3350 to +3352
printf("BEGIN\n");
while(*kexp && (*kexp)->name) {
s = ssh2_kex_agree_instr(kex, kex_len,
(const unsigned char *)(*kexp)->name,
strlen((*kexp)->name));
if(s) {
/* We have agreed on a key exchange method,
* Can we agree on a hostkey that works with this kex?
*/
if(kex_agree_hostkey(session, (*kexp)->flags, hostkey,
hostkey_len) == 0) {
session->kex = *kexp;
if(session->burn_optimistic_kexinit && kex == s)
/* Server sent an optimistic packet, and client agrees
* with preference cancel burning the first KEX_INIT
* packet that comes in */
session->burn_optimistic_kexinit = 0;
printf("ITER: |%s|%p|\n", (*kexp)->name, (void *)(*kexp)->exchange_keys);
Copilot AI review requested due to automatic review settings July 23, 2026 00:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/kex.c:3352

  • Unconditional printf() debug output was added to this library code path. This is noisy for API consumers and can also fail compilation because kex.c does not include <stdio.h> (implicit printf declaration). Remove these debug prints; use existing ssh2_deb() tracing if runtime diagnostics are needed.
    printf("BEGIN\n");
    while(*kexp && (*kexp)->name) {
        printf("ITER: |%s|%d|\n", (*kexp)->name, (*kexp)->exchange_keys ? 1 : 0);

src/kex.c:3360

  • More unconditional printf() debug statements were added inside the negotiation loop. These should not ship in a library; they spam stdout/stderr and may break builds depending on compiler flags. Remove the printf() calls and keep only the negotiation logic.
        if((*kexp)->exchange_keys) {
            printf("OK\n");
            s = ssh2_kex_agree_instr(kex, kex_len,
                                     (const unsigned char *)(*kexp)->name,
                                     strlen((*kexp)->name));

src/kex.c:3384

  • Remove the unconditional printf() debug output before returning -1; this is library code and should not write to stdout.
    printf("END=-1\n");
    return -1;

Copilot AI review requested due to automatic review settings July 23, 2026 00:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/kex.c:3352

  • Remove the unconditional stderr/stdout debug prints in this negotiation path. Writing directly to stdout/stderr from a library breaks embedding applications and can leak handshake details; if tracing is needed it should go through the existing libssh2 debug/trace mechanism (ssh2_deb/LIBSSH2_TRACE_KEX).
    fprintf(stderr, "BEGIN\n");
    while(*kexp && (*kexp)->name) {
        printf("ITER: |%s|%d|\n", (*kexp)->name, (*kexp)->exchange_keys ? 1 : 0);

src/kex.c:3363

  • Remove the remaining stdout debug prints inside the agreement path (AGREE-1/AGREE-2/RETURN 0). These side effects are not appropriate in production library code and bypass the project's existing tracing facilities.
            if(s) {
                printf("AGREE-1\n");
                /* We have agreed on a key exchange method,

src/kex.c:3384

  • Remove the final stdout debug print before returning failure. This function should remain side-effect free aside from setting negotiation state.
    printf("END=-1\n");
    return -1;

Copilot AI review requested due to automatic review settings July 23, 2026 00:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/kex.c:3357

  • This block still contains unconditional debug output (fprintf/printf) and the condition (*kexp)->exchange_keys || 1 is always true, which defeats the intended skipping of signaling-only pseudo-KEX entries. Remove the debug prints and gate negotiation on exchange_keys only.
         * and must never be selected as the negotiated method. */
        if((*kexp)->exchange_keys || 1) {
            printf("OK\n");

src/kex.c:3366

  • Remove the remaining debug prints inside the negotiation path; writing to stdout/stderr from a library breaks callers and is not appropriate here.
                printf("AGREE-1\n");
                /* We have agreed on a key exchange method,
                 * Can we agree on a hostkey that works with this kex?
                 */
                if(kex_agree_hostkey(session, (*kexp)->flags, hostkey,

src/kex.c:3376

  • Remove this leftover debug print; it causes unexpected stdout output from libssh2 during normal negotiation.
                    printf("RETURN 0\n");

src/kex.c:3383

  • Remove this leftover debug print before returning failure; libssh2 should not write to stdout in error paths.
    printf("END=-1\n");

@vszakats

Copy link
Copy Markdown
Member Author

The fuzzer seems to hang right on start with this change in,
without ever showing the printf() traces. Or maybe not right
on start but indeed an infinite loop combined with CI capture/
logging issue.

vszakats added a commit to vszakats/libssh2 that referenced this pull request Jul 23, 2026
"The signalling-only KEX entries are skipped in the default
`kex_methods` loop, but the `session->kex_prefs` negotiation path can
still select a method with `exchange_keys == NULL` (e.g., if a server
incorrectly advertises `ext-info-c` / `kex-strict-c-v00@openssh.com`).
With the new hard-failure in `ssh2_kex_exchange()`, this would cause an
avoidable connection failure instead of falling through to the next real
KEX method. Consider adding the same `exchange_keys` filter to the
`session->kex_prefs` loop before calling `kex_agree_hostkey()`/setting
`session->kex`."

libssh2#2199 (comment)

"The new exchange_keys filter correctly avoids selecting signalling-only
KEX entries when session->kex_prefs is NULL, but the session->kex_prefs
negotiation path above can still select ext-info-c /
kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this
file). Since libssh2_session_method_pref() prepends these extensions to
user KEX prefs, users who set KEX prefs can still end up negotiating a
method with no handler and now hit the new runtime error in
ssh2_kex_exchange(). The prefs-driven loop should also skip methods with
exchange_keys == NULL so it continues searching for a real KEX
implementation."
Copilot AI review requested due to automatic review settings July 23, 2026 12:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/kex.c:3351

  • Remove the debug I/O to stderr/stdout during KEX negotiation. Writing to stdio from a library is not acceptable and can corrupt an application’s output or logs.
    fprintf(stderr, "BEGIN\n");
    while(*kexp && (*kexp)->name) {
        printf("ITER: |%s|%d|\n", (*kexp)->name, (*kexp)->exchange_keys ? 1 : 0);

src/kex.c:3359

  • Remove the remaining debug printf() calls from the negotiation loop. If additional visibility is needed, use the existing libssh2 tracing (e.g., ssh2_deb/LIBSSH2_TRACE_KEX) rather than unguarded stdio output.
        if((*kexp)->exchange_keys) {
            printf("OK\n");
            s = ssh2_kex_agree_instr(kex, kex_len,
                                     (const unsigned char *)(*kexp)->name,
                                     strlen((*kexp)->name));

src/kex.c:3383

  • Remove the final debug print at function exit; it introduces unwanted stdout output from the library.
    }
    printf("END=-1\n");
    return -1;

@vszakats

Copy link
Copy Markdown
Member Author

The fuzz hanger happened in another PR touching the kex_agree*
logic. It says to me that this isn't stricly related to this patch, or rather
that both patches touch the same weak point either in the agree code,
the fuzzer or the two interacting with each other.

Ref: #2444

vszakats added 4 commits July 29, 2026 14:16
"The signalling-only KEX entries are skipped in the default
`kex_methods` loop, but the `session->kex_prefs` negotiation path can
still select a method with `exchange_keys == NULL` (e.g., if a server
incorrectly advertises `ext-info-c` / `kex-strict-c-v00@openssh.com`).
With the new hard-failure in `ssh2_kex_exchange()`, this would cause an
avoidable connection failure instead of falling through to the next real
KEX method. Consider adding the same `exchange_keys` filter to the
`session->kex_prefs` loop before calling `kex_agree_hostkey()`/setting
`session->kex`."

libssh2#2199 (comment)

"The new exchange_keys filter correctly avoids selecting signalling-only
KEX entries when session->kex_prefs is NULL, but the session->kex_prefs
negotiation path above can still select ext-info-c /
kex-strict-c-v00@openssh.com (both have exchange_keys == NULL in this
file). Since libssh2_session_method_pref() prepends these extensions to
user KEX prefs, users who set KEX prefs can still end up negotiating a
method with no handler and now hit the new runtime error in
ssh2_kex_exchange(). The prefs-driven loop should also skip methods with
exchange_keys == NULL so it continues searching for a real KEX
implementation."
This reverts commit 7a1491e.
vszakats added 4 commits July 29, 2026 14:17
This reverts commit 38f6997.
This reverts commit abaf8d3.
@vszakats vszakats changed the title kex: skip signaling kex entries when negotiating kex: skip signaling-type kex entries when negotiating Aug 7, 2026
@vszakats
vszakats requested a balanced review from Copilot August 7, 2026 19:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/kex.c:3329

  • This security-sensitive negotiation regression has no automated coverage. Please add a test where the peer advertises a shared signaling entry alongside a real KEX method and verify that the real method is selected (including the custom kex_prefs path), so a future refactor cannot restore the null-exchange_keys selection.
                if(method->exchange_keys &&
                   kex_agree_hostkey(session, method->flags, hostkey,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants