Refactor record padding handling to eliminate middle padding pattern#10149
Refactor record padding handling to eliminate middle padding pattern#10149julek-wolfssl wants to merge 11 commits into
Conversation
240bdfa to
29a1c54
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Refactors TLS record padding handling so padSz is accounted for once per record (in ProcessReply/DoProcessReplyEx) instead of being applied and “corrected” within each individual message handler.
Changes:
- Removes per-message
padSzindex advancement across TLS 1.3 and (D)TLS message handlers. - Adjusts
DoProcessReplyExto treatcurSizeas content-only by subtractingpadSz, and addspadSzonce when the record is fully processed. - Updates multi-message record flow to eliminate the prior “middle padding” correction, and restricts buffer shrinking during multi-message processing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/tls13.c | Removes per-handler padSz advancement so TLS 1.3 handlers advance by content length only. |
| src/internal.c | Centralizes padSz accounting in DoProcessReplyEx, updating message parsing logic and removing old padding correction paths. |
Comments suppressed due to low confidence (4)
src/internal.c:1
- Subtracting
padSzfromssl->curSizewithout first verifyingcurSize >= padSzcan underflow on malformed/truncated records and corrupt subsequent parsing/state. Add a guard (e.g., ifssl->curSize < ssl->keys.padSzreturn a length/buffer error) before performing the subtraction.
src/internal.c:1 - Advancing
inputBuffer.idxbypadSzhas no bounds validation. If padding/tag bytes are missing (corrupted/truncated input),idxcan jump beyond the actual buffered record data and leave the connection state inconsistent. Add a bounds check before incrementing (e.g., ensureidx + padSzdoes not exceed the available buffered data for the current record) and fail with a parse error if it would.
src/internal.c:1 inputLengthis reduced bypadSzwithout checking thatinputLength >= padSz. When the remaining buffered bytes are smaller thanpadSz, this will underflow (likely to a very largeword32) and can cause oversized copies/allocations or out-of-bounds reads later. Add an explicit check before subtracting and error out when insufficient bytes remain.
src/internal.c:1- The parameter
sizeis used (if (size)), so(void)size;is redundant and can be removed to reduce confusion.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Retest this please history lost..... |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/internal.c:1
ssl->curSizeis an unsigned type; subtractingpadSzcan underflow ifpadSz > curSize(e.g., malformed/short records or ifpadSzis unexpectedly large), which would inflatecurSizeand break subsequent bounds checks (including the newcurStartIdx + curSizecalculations). Make this subtraction conditional onssl->curSize >= ssl->keys.padSz(return an error likeBUFFER_E/LENGTH_ERRORotherwise), and avoid truncatingpadSztoword16when comparing/subtracting.
src/internal.c:1- This DTLS 'drop record outside receiving window' path adds
ssl->keys.padSzunconditionally, butcurSizeis only reduced bypadSzwhenIsEncryptionOn(ssl, 0)is true. If encryption is off (or ifpadSzis stale from a prior encrypted record), this can skip too far and desynchronize parsing. AddpadSzhere only when the current record is encrypted (or ensurepadSzis reset to 0 for plaintext records before this path).
src/internal.c:1 - After advancing
inputBuffer.idxbypadSz, the code only checks>= length(and returns), but does not preventidxfrom becoming> length. LeavinginputBuffer.idxpast the buffer end can violate invariants for subsequent calls/paths. Add an explicitif (idx > length) return BUFFER_E;(or equivalent) immediately after theidx += padSzincrement (and similarly in otherpadSz-advance early-return paths).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
dgarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: APPROVE
Findings: 4 total — 3 posted, 2 skipped
Posted findings
- [Medium] Missing defensive guard before
curSize -= padSzcould allow word16 underflow —src/internal.c:23184-23185 - [Medium] Callers of message handlers still pass
inputBuffer.lengthas totalSz, not content boundary —src/internal.c:23286-23289,23306-23309,23343-23346,23360-23362 - [Medium] No dedicated test for multi-message encrypted record processing after refactoring —
src/internal.c:23654-23695
Skipped findings
- [Low] Truncation cast of word32 padSz to word16 suppresses potential warnings
- [Medium] Callers of message handlers still pass
inputBuffer.lengthas totalSz, not content boundary
Review generated by Skoll via openclaw
|
|
||
| /* Reduce curSize to content only, excluding padding/MAC overhead. | ||
| * padSz is accounted for once at the end of record processing. */ | ||
| if (IsEncryptionOn(ssl, 0)) |
There was a problem hiding this comment.
🟡 [Medium] Missing defensive guard before curSize -= padSz could allow word16 underflow
💡 SUGGEST bug
The new centralized padding subtraction ssl->curSize -= (word16)ssl->keys.padSz performs unsigned word16 arithmetic. If padSz exceeds curSize due to a malformed record or bug in MAC verification, this underflows to a very large value (e.g., curSize=50, padSz=100 → curSize=65486).
For TLS 1.3, removeMsgInnerPadding guarantees padSz <= curSize by construction (scan-based). For non-TLS-1.3, VerifyMac should guarantee this, but there is no explicit check at the subtraction site.
Importantly, the MAX_PLAINTEXT_SZ check at lines 23245/23263 catches the underflow for pre-TLS-1.3 (ETM and non-ETM paths). However, for TLS 1.3 these checks are skipped (comment at line 23261: 'TLS13 plaintext limit is checked earlier before decryption'), so if the invariant were ever violated in TLS 1.3, the underflowed curSize would propagate to message handlers unchecked.
Suggestion:
| if (IsEncryptionOn(ssl, 0)) | |
| /* Reduce curSize to content only, excluding padding/MAC overhead. | |
| * padSz is accounted for once at the end of record processing. */ | |
| if (IsEncryptionOn(ssl, 0)) { | |
| if (ssl->keys.padSz > ssl->curSize) | |
| return BUFFER_E; | |
| ssl->curSize -= (word16)ssl->keys.padSz; | |
| } |
| @@ -23710,53 +23653,44 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) | |||
|
|
|||
| ssl->options.processReply = doProcessInit; | |||
There was a problem hiding this comment.
🟡 [Medium] No dedicated test for multi-message encrypted record processing after refactoring
💡 SUGGEST test
The core change in this PR is eliminating the 'middle padding' correction pattern where padding was added per-handler and then corrected at record end. The existing test suite provides indirect coverage through standard TLS/DTLS handshakes (e.g., test_records_span_network_boundaries, test_tls13_middlebox_compat_empty_session_id), but there is no dedicated test that:
- Constructs a single TLS 1.2 encrypted record containing multiple handshake messages
- Verifies all messages are processed correctly
- Verifies the index advances by exactly
content_size + padSzat the end
This refactoring removes ~15 separate *inOutIdx += ssl->keys.padSz call sites and replaces them with a single centralized addition. A regression in any handler's index advancement would cause subtle protocol-level failures that might not be caught by existing tests.
Recommendation: Add a targeted test that sends a TLS 1.2 encrypted record with multiple handshake messages (e.g., Certificate + ServerKeyExchange in a single record) and verifies correct processing. This would directly exercise the refactored multi-message record loop and catch any regression in the centralized padding handling.
There was a problem hiding this comment.
Added a test in scripts/multi-msg-record.test.
|
Failures unrelated to PR. |
|
Retest this please Jenkins |
JacobBarthelmeh
left a comment
There was a problem hiding this comment.
This appears to be a good refactor. I need help on closer review of DTLS 1.3 changes though. @rizlik could you provide an additional review?
|
retest this please |
| unset AM_BWRAPPED | ||
| fi | ||
|
|
||
| # Locate python3 – skip (exit 77) if unavailable. |
There was a problem hiding this comment.
Check for non ASCII here:
[check-source-text] [5 of 41] [3caf4b9da9]
configure... real 0m8.506s user 0m5.226s sys 0m4.103s
8 bit:
./scripts/multi-msg-record.test:29:# Locate python3 ¶ skip (exit 77) if unavailable.
rizlik
left a comment
There was a problem hiding this comment.
Very nice!
Can you add a github action that install python script dependencies and run the test? My fear is that this test might be skipped because of the missing dependency and the multimessage is left untested in our CI.
3caf4b9 to
b2ab734
Compare
|
@rizlik added python setup to relevant workflows. |
|
Move padSz index advancement from individual message handlers to a single location at the end of record processing in ProcessReply. Previously, each handler (DoFinished, DoApplicationData, DoAlert, DoCertificate, DoServerHello, etc.) advanced the index by padSz, which then had to be corrected when processing multiple messages in one record. This "middle padding" pattern was error-prone. Now curSize is reduced by padSz after decryption/verification so it reflects content size only. Message handlers advance the index by content size only. padSz is added once when the record is fully processed. The correction code for multi-message records is removed.
Since ProcessReply already reduces curSize by padSz after decryption, use curStartIdx + curSize to bound content data instead of recomputing it from buffer.length - padSz. This removes three more padSz references from message processing code.
Add bounds check before computing inputLength from curStartIdx + curSize to prevent unsigned underflow if *inOutIdx ever exceeds the record content boundary.
Probe ./client -e for the supported cipher list and skip suites that aren't compiled in instead of reporting them as failures.
wolfSSL builds configured with --enable-coding=no cannot parse PEM because base64 decoding is disabled. Switch the example client's -A argument to ca-cert.der so the test works in both PEM-enabled and PEM-disabled builds.
multi-msg-record.py: auto-detect the CA cert format the wolfSSL client
build accepts (PEM or DER) from the default shown in client -? help.
OPENSSL_EXTRA-style builds need PEM; NO_CODING builds need DER.
ocsp-stapling.test: skip the external login.live.com connection unless
WOLFSSL_EXTERNAL_TEST is explicitly enabled (matches external.test /
google.test convention). Local OCSP tests still run.
ocsp-responder-openssl-interop.test: use ${TMPDIR:-/tmp} for mktemp
templates so the test works when /tmp is not writable.
The test certs are RSA; if NO_RSA is defined the client can neither load nor verify them. Detect "RSA not supported" in client -? help and exit 77 (SKIP) before tlslite-ng tries to use the RSA chain.
abd7ddf to
39642d5
Compare
Move padSz index advancement from individual message handlers to a
single location at the end of record processing in ProcessReply.
Previously, each handler (DoFinished, DoApplicationData, DoAlert,
DoCertificate, DoServerHello, etc.) advanced the index by padSz,
which then had to be corrected when processing multiple messages
in one record. This "middle padding" pattern was error-prone.
Now curSize is reduced by padSz after decryption/verification so
it reflects content size only. Message handlers advance the index
by content size only. padSz is added once when the record is fully
processed. The correction code for multi-message records is removed.