internal, tests: start no highwater rekey after an inbound packet fails - #1279
Open
yosuke-wolfssl wants to merge 1 commit into
Open
yosuke-wolfssl wants to merge 1 commit into
yosuke-wolfssl wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation consistently covers unrecoverable receive paths and includes focused regression tests.
Review effort: Balanced
Findings: None
What changed in this PR
Prevents highwater-triggered rekeying after unrecoverable inbound packet validation failures.
Changes:
- Disables byte/message highwater callbacks after framing, decryption, or integrity failures.
- Clarifies highwater flag and send-error semantics.
- Adds regression coverage for malformed packets and post-failure sends.
| File | Description |
|---|---|
src/internal.c |
Suppresses highwater callbacks after fatal packet rejection. |
wolfssh/internal.h |
Documents expanded highwater flag semantics. |
tests/unit.c |
Tests malformed packets and callback suppression. |
💡 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 #1279
Scan targets checked: wolfssh-src, wolfssh-bugs
Coverage: 2 of 3 in-scope changed file(s) opened by the reviewer; not opened: wolfssh/internal.h
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
Review tier: Lite
- DoReceive() sets highwaterFlag and msgHighwaterFlag when an inbound packet fails decryption, its MAC, or its length or alignment check. - DoPacket() sets both flags when padding_length is below MIN_PAD_LENGTH or leaves no room for the message id. - The highwaterFlag and msgHighwaterFlag comments name that writer. - The wolfSSH_SendPacket() comment limits its ssh->error claim to the flush and says HighwaterCheck() writes nothing there but its callback may. - unit.c adds test_HighwaterQuietAfterBadPacket(): an idle worker pass fires the highwater callback, and a bad-MAC worker pass and the send after it do not. - unit.c adds test_DoReceive_RejectsPaddingUnderflow(), and the VerifyMacFailure, AeadTagFailure, RejectsShortPadding, RejectsMisalignedPacket and WorkerHardRecvErrorOutranksFlush tests check both flags are set. VerifyMacFailure clears them per case.
yosuke-wolfssl
force-pushed
the
fix/highwater
branch
from
September 29, 2026 04:35
2c47bd6 to
6f6a4e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
wolfSSH_worker()flushes queued output whateverDoReceive()returned (deliberate since #1217: an idle receive and a hard failure both returnWS_FATAL_ERROR). The flush goes throughwolfSSH_SendPacket(), which runs the transmitHighwaterCheck(), and the default callback callswolfSSH_TriggerKeyExchange(). So when the mark comes due on a pass whose inbound packet failed its MAC or decrypt check, the library builds and sends a new KEXINIT and setsisKeyingon a session whose input can no longer be read.Fixing only the worker would not be enough. The check stays armed until it fires, so the KEXINIT would move to the next send, typically the CHANNEL_EOF/CLOSE that
wolfSSH_shutdown()sends during teardown.Separately, the
wolfSSH_SendPacket()comment said transport failures record their code inssh->error, which is not true of what the highwater callback returns.Fix (
src/internal.c)The receive paths that reject the peer's bytes set
highwaterFlagandmsgHighwaterFlag, so no highwater callback fires for the rest of the session.HighwaterCheck()already skips a mark whose flag is set, and onlyDoNewKeys()clears the flags. No NEWKEYS can be processed after such a failure: the rejected packet is never consumed, so every retry fails on it again.DoReceive()Decrypt(),packet_lengthtoo large, not block aligned, bodyDecrypt(),VerifyMac(),DecryptAead()DoPacket()padding_lengthbelowMIN_PAD_LENGTH, or leaving no room for the message idThe length and alignment checks are included because, under CTR, a flipped bit in an encrypted
packet_lengthfails them beforeVerifyMac()runs.DoPacket()errors that consume the packet are not covered: the input stays in sync and a rekey is still legitimate. The worker andSendPacketFlush()are unchanged. ThewolfSSH_SendPacket()comment now covers flush failures only, and saysHighwaterCheck()writes nothing tossh->errorbut its callback may.Behavior change: before, a mark crossed after an integrity failure sent a KEXINIT that could never complete, and
SendChannelData()then refused withWS_REKEYINGfor the rest of the session. Now it doesn't, so an app that ignores the worker'sWS_FATAL_ERROR+WS_VERIFY_MAC_Eand keeps sending can go past the mark under the same key.ssh.halready classes that pair as an error, not a transient. A highwater callback used only for notification is also no longer called after such a failure.Tests
test_HighwaterQuietAfterBadPacket(): an idle worker pass fires the callback; a bad-MAC pass still flushes the queued output but fires nothing, and neither does the nextwolfSSH_SendIgnore().test_DoReceive_RejectsPaddingUnderflow()covers the one reachable check no existing test reached.DoReceiverejection tests andtest_WorkerHardRecvErrorOutranksFlush(), plus the new underflow test.Verification
Decrypt()checks have no test, because a peer cannot make them fail: by then sizes are block-aligned, so only an internal or hardware crypto error reaches them.unit.test172 passed / 0 failed.regress,testsuite,apiand thescp,sshclient,get-putandsftpscripts pass.-Werroracross 6 configs.