ssh: add a rekey-state accessor - #1260
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The API addition and keying-flag gating change are consistent with existing worker/error semantics, and the PR includes targeted regression/unit coverage for the new behavior and the previously problematic edge case.
Pull request overview
This PR adds a small public API to let consumers query whether a key exchange (rekey) is currently in flight, and fixes a keying-state edge case where SendKexInit() could leave the session stuck “keying” even when the KEXINIT send failed outright.
Changes:
- Add
wolfSSH_RekeyPending(const WOLFSSH*)accessor (NULL-safe, puressh->isKeyingread) and document its intended use alongsidewolfSSH_worker()/wolfSSH_get_error(). - Adjust
SendKexInit()to setWOLFSSH_SELF_IS_KEYINGonly once the KEXINIT packet is successfully sent or queued, avoiding a stale keying flag on outright send failure. - Add regression/unit tests for the accessor and for the “KEX init send fails / short-writes” keying-flag behavior; remove an export-visibility check from
tests/testsuite.c.
File summaries
| File | Description |
|---|---|
| wolfssh/ssh.h | Documents the contract and adds the public wolfSSH_RekeyPending() declaration. |
| wolfssh/internal.h | Updates the semantics comment for WOLFSSH_SELF_IS_KEYING to match the new gating. |
| src/ssh.c | Implements wolfSSH_RekeyPending() as a NULL-safe isKeying predicate. |
| src/internal.c | Moves WOLFSSH_SELF_IS_KEYING set to only after KEXINIT is sent/queued. |
| tests/unit.c | Adds assertions covering predicate behavior and the KEXINIT send-failure/short-write keying gate. |
| tests/testsuite.c | Removes the wolfSSH_OutputPending() export check from the testsuite harness. |
| tests/regress.c | Adds regression coverage for wolfSSH_RekeyPending() over NULL and keying-bit combinations. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
0b337ca to
52bd594
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Error: RuntimeError
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Error: RuntimeError
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Error: RuntimeError
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Error: RuntimeError
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1260
Error: RuntimeError
2551373 to
f2772c0
Compare
f2772c0 to
9a64e61
Compare
- wolfSSH_RekeyPending() reports whether a key exchange is in flight, the first one included, returning 0 for a NULL session. - the wolfSSH_worker() block in ssh.h names it as the way to ask, alongside wolfSSH_OutputPending(), and the prototype notes that the flag stays set after a failed exchange, so a loop on it must also end on wolfSSH_worker(). - tests/regress.c covers each keying bit alone, both together, and a NULL session for both predicates. - tests/testsuite.c drops its wolfSSH_OutputPending() call, the only such export check among the public functions.
- SendKexInit() takes ssh->txFlushCount before SendPacketFlush() and sets WOLFSSH_SELF_IS_KEYING when SendPacketDelivered() reports the packet away, in place of setting it before the packet is built. - PurgePacket() runs on the same decision rather than on the send's return. - SendKexInit() runs HighwaterCheck() itself, after the flag is set, in place of taking it from wolfSSH_SendPacket(). - internal.h describes the flag as set once the KEX init is sent or queued. - tests/unit.c covers a KEX init the transport rejects, one it refuses with a would-block, and one it takes whole behind a highwater callback that fails or reads the keying flag; the first two also check whether the packet is left owed. - HwTestCb() also records wolfSSH_RekeyPending() for the session its HwTestCtx names. - FailHighwater(), s_sendRefusals and RefuseThenResetIoSend() move beside the other shared test callbacks and gain WS_MAYBE_UNUSED, so the client-side test compiles without the server.
9a64e61 to
c12d294
Compare
Fenrir won't run for this PR. Skoll calls it ready to merge.
Problem
Consumers have no way to ask whether a key exchange is running. 24 of the 118
consumer reads of
wolfSSH_get_error()/ssh->errorask exactly that; 21 areone repeated pump in
examples/sftpclient/sftpclient.c, the only consumer codethat reads the
WOLFSSHstruct directly.wolfSSH_worker()withholdsWS_REKEYINGwhen its flush failed, so that pump can exit mid-rekey.SendKexInit()also setWOLFSSH_SELF_IS_KEYINGbefore building the packet, soa send that never reached the transport left the session believing a rekey was
in flight.
DoKexInit()gates the answering KEXINIT on that flag, so the peerthen waits forever for a KEXINIT we had already discarded.
Fix (
src/internal.c,src/ssh.c)wolfSSH_RekeyPending(const WOLFSSH*)returns nonzero during a keyexchange, the first one included, and 0 otherwise or for a NULL session. As a
pure
ssh->isKeyingread it cannot disagree withwolfSSH_worker()'sWS_REKEYING; only NEWKEYS from both sides clears it, so the prototype notesa loop on it must also end on
wolfSSH_worker().SendKexInit()flushes withSendPacketFlush(), sets the flag whenSendPacketDelivered()reports the packet sent or queued, and purges on thesame decision. It then runs
HighwaterCheck()itself, so a callback fired bythe KEXINIT's own flush sees the flag set; the return is unchanged. This is the
pattern
SendGlobalRequestFwd()already uses.Tests
tests/regress.ctests/unit.cVerification
-Werrorconfigs,--disable-serverand--disable-client.unit170 against a baseline of 169, plusregress,testsuite,kex,apiandauth; thescp,sshclient,get-putandsftpscripts pass.forcing
PurgePacket(), setting the flag afterHighwaterCheck(), gating onthe send's return, and running
HighwaterCheck()after a failed send.Rebase note
25513731(@ejohnstown) is folded into this branch: its placement of the sharedsend mock and its
s_sendRefusalscomment are in verbatim. The rebase removedthe commit, not the change.
Not in this PR
change against a settled contract, not alongside it.
tests/testsuite.cdrops itswolfSSH_OutputPending()probe, the onlyexport check among the public functions; covering all of them is not worth it.