internal: reject inbound packets that are not block aligned - #1189
Open
yosuke-wolfssl wants to merge 1 commit into
Open
internal: reject inbound packets that are not block aligned#1189yosuke-wolfssl wants to merge 1 commit into
yosuke-wolfssl wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens inbound SSH packet parsing by enforcing RFC-required packet block alignment in DoReceive(), ensuring malformed packets are rejected consistently across cleartext/null, CBC/CTR, and AEAD (AES-GCM) transport paths.
Changes:
- Add a block-alignment check in
src/internal.cduringPROCESS_PACKET_LENGTH, before decrypt/dispatch. - Fix test fixtures that previously emitted RFC-invalid packet sizes and add targeted negative tests for misalignment (cleartext + AES-GCM).
- Adjust existing AEAD/HMAC unit tests to use correctly aligned packet prefixes and correct AEAD block size assumptions.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/internal.c |
Rejects inbound packets whose length violates RFC block-alignment rules before any decrypt/MAC/dispatch. |
tests/unit.c |
Updates packet-building helpers/fixtures and adds unit tests asserting misaligned packets are rejected (cleartext + AES-GCM). |
tests/regress.c |
Updates minimal packet builder to generate RFC-valid, block-aligned packets for clear transport fixtures. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- DoReceive() validates the peeked packet_length in PROCESS_PACKET_LENGTH: UINT32_SZ plus curSz for non-AEAD, curSz alone for AEAD, against peerBlockSz floored at MIN_BLOCK_SZ. A non-zero remainder sets ssh->error to WS_BUFFER_E and returns WS_FATAL_ERROR. - BuildPacket() in regress.c and BuildMacTestPacketPrefix() in unit.c pad to a block-aligned total, the latter taking padLen from the caller; test_DoReceive_VerifyMacFailure, test_DoReceive_AeadTagFailure, and test_DoReceive_RejectsShortPadding follow. - test_DoReceive_RejectsMisalignedPacket and test_DoReceive_RejectsMisalignedAead cover the cleartext and AES-GCM paths. Issue: F-8834
yosuke-wolfssl
force-pushed
the
fix/f_8834
branch
from
August 21, 2026 00:28
4cf6c5f to
988cfeb
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
DoReceive()accepted an inbound binary packet on a maximum-length check alone. Nothing verified the RFC 4253 §6 rule thatpacket_length + padding_length + payload + paddingbe a multiple of the cipher block size, or 8, whichever is larger. RFC 5647 imposes the same rule on AES-GCM, excluding the length field.What coverage existed was incidental. AES-CBC/CTR were aligned only as a side effect of
Decrypt()'ssz % AES_BLOCK_SIZE, and only when the packet spanned more than one block. AES-GCM had no check at all. Neither did the null cipher — which every connection runs for the whole pre-NEWKEYS handshake, in every build, with no MAC to backstop it. So KEXINIT, KEXDH, and NEWKEYS were all accepted at arbitrary lengths.Closes f-8834.
Fix (
src/internal.c)One check in
DoReceive()'sPROCESS_PACKET_LENGTHcase, placed before any decrypt or dispatch so it covers every cipher path:UINT32_SZ + packet_lengthUINT32_SZ + packet_lengthpacket_lengthpeerBlockSzis floored atMIN_BLOCK_SZ. A misaligned packet setsssh->errortoWS_BUFFER_Eand returnsWS_FATAL_ERROR— the codeDoPacket()already returns for the siblingMIN_PAD_LENGTHviolation. The rule mirrors whatBundlePacket()has always emitted on the send side, so no conformant peer is affected.Tests
test_DoReceive_RejectsMisalignedPacketandtest_DoReceive_RejectsMisalignedAeadcover the cleartext and AES-GCM paths. Each packet is valid in every other respect, so alignment is the only thing that can reject it.Reviewers should expect fixture churn:
BuildPacket()(regress.c, 10 call sites) andBuildMacTestPacketPrefix()(unit.c) both aligned the packet body to 8 and left the total at 12, i.e. they were emitting packets RFC 4253 forbids. The new check didn't break those tests — it revealed they were testing against malformed input. Both now pad to a block-aligned total, andtest_DoReceive_AeadTagFailureuses the real GCMpeerBlockSzof 16 rather than 4.Verification
-Werrorclean across 6 configs; lint clean.unit.testandregress.testpass.make check: 10 pass, 1 skip; the lone failure isscripts/sftp.test, a known parallel-run flake that passes serially.DoReceiveaccepting the malformed packet.