F-8045 - Reject duplicate SCEP signed attributes - #20
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens SCEP pkiMessage parsing by rejecting duplicate signed attributes (per RFC 8894) to prevent memory leaks and attribute-confusion behavior when wolfSSL’s PKCS#7 decoder returns multiple attributes with the same OID.
Changes:
- Add per-OID “seen” tracking in
wolfcert_scep_parse_pki_messageand returnWOLFCERT_ERR_PROTOCOLon duplicate signed attributes. - Centralize OID→attribute mapping via
scep_attr_bit()and simplify per-attribute output handling. - Add a unit test that constructs a SignedData with duplicate
transactionIDattributes and asserts rejection with no leftover allocations.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/scep/scep_msg.c |
Rejects duplicate SCEP signed attributes and rolls back any outputs on protocol-malformed input. |
tests/unit/test_scep_msg.c |
Adds a regression test that encodes a pkiMessage with duplicate transactionID to ensure it is rejected and outputs are cleared. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- scep_attr_bit() maps a decoded attribute OID to one of six bits for messageType, pkiStatus, failInfo, transactionID, senderNonce and recipientNonce, and returns 0 for anything else. - wolfcert_scep_parse_pki_message() accumulates those bits in and returns WOLFCERT_ERR_PROTOCOL on a repeat; that path frees and NULLs every requested out-parameter and the envelope. - The six copy branches become a switch selecting the out-parameter, and the walk's locals move to the top of the function. - test_scep_msg gains make_dup_tid_signed() and test_duplicate_signed_attrib(), which build a SignedData carrying a messageType and two transactionID attributes, request every out-parameter, and require the parse to reject it with all of them returned NULL. Issue: F-8045
5b32b79 to
09d4ce9
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #20
Scan targets checked: wolfcert-bugs, wolfcert-src
No new issues found in the changed files. ✅
Frauschi
left a comment
There was a problem hiding this comment.
The duplicate-attribute fix looks right to me. One gap worth closing while you are in here: the repeated-SEQUENCE case is covered, but a single attribute carrying two values in its SET still slips through. Comment inline.
| "duplicate signed attribute in pkiMessage"); | ||
| break; | ||
| } | ||
| seen |= bit; |
There was a problem hiding this comment.
The seen bitmask catches the same OID appearing in two Attribute SEQUENCEs, but not two AttributeValues inside one attribute's SET. wc_PKCS7_ParseAttribs creates one PKCS7DecodedAttrib per SEQUENCE and copies the SET's content into value with valueSz covering the whole SET - it never iterates the values inside. So transactionID ::= SET { PrintableString "A", PrintableString "B" } yields one list node, one bit, and no collision. The unwrap below then reads the first value and the voff + vlen > a->valueSz check lets the trailing bytes through, so the message is accepted with *out_transaction_id == "A".
Not a leak, and the first-value pick is pre-existing rather than a regression - but it does mean a peer can still hand handle_pki_op and scep_finish two values for one attribute and have us act on one of them without complaint. RFC 8894 defines each of these as single-valued, so by the same argument the rest of this change makes, I'd require the value to fill the SET:
if (voff + vlen != a->valueSz) {
rc = WOLFCERT_ERR(WOLFCERT_ERR_PROTOCOL, "scep",
"multi-valued signed attribute in pkiMessage");
break;
}That equality holds in both shapes the code handles, and it reuses the reject path you already added, so there is no extra rollback to write. Covering it in test_duplicate_signed_attrib needs a hand-encoded SignedData - wolfSSL's EncodeAttributes emits one SET per PKCS7Attrib and cannot produce the input.
Problem
wolfcert_scep_parse_pki_messagewalked the whole decoded signed-attribute list and, for each of the six recognised SCEP OIDs, allocated a copy and assigned it straight over the out-parameter with no check that the parameter was already populated. wolfSSL's PKCS#7 decoder does not de-duplicate attributes by OID, so a pkiMessage carrying N copies of one orphaned N-1 allocations — the caller only ever received, and freed, the last pointer. The peer also decided which copy won.Both sides of the protocol reach it. Server:
handle_pki_opparses a client-supplied body, and since the client self-signs its pkiMessage it controls the attribute SET while still satisfyingwc_PKCS7_VerifySignedData. Client:scep_finishparses the response before the signer trust check, so an attacker answering a plaintext SCEP endpoint leaks memory on every attempt even though the forged CertRep is ultimately rejected. The 1 MiB body cap bounds each request, making this memory exhaustion under sustained traffic rather than an immediate crash.The finding lists three production call sites; only two leak.
wolfcert_scep_verify_next_ca_responsepasses NULL for every attribute out-parameter and never allocated.Fix (
src/scep/scep_msg.c)scep_attr_bit()maps a decoded attribute OID to one of six bits, so the OID list exists in exactly one place.seenaccumulates those bits; a repeat returnsWOLFCERT_ERR_PROTOCOL. RFC 8894 defines each attribute as singular, so rejecting also removes the attribute-confusion primitive that first-wins-skip would leave behind.switchselecting the target out-parameter.Closes f-8045.
Test harness
test_duplicate_signed_attribintests/unit/test_scep_msg.cdriveswc_PKCS7_EncodeSignedDatawith amessageTypeplus twotransactionIDattributes — well-formed DER that is protocol-malformed, so it survives signature verification and reaches the walk. It requests every out-parameter and requiresWOLFCERT_ERR_PROTOCOLwith all of them returned NULL.Verification
-Werror.WOLFCERT_OK, andleaksreports the orphaned copy.messageTypeand signer-certificate frees yields exactly two orphans, 16 B and 768 B.Not in this PR
The copy sites still leave the out-parameter NULL when
WOLFCERT_XMALLOCfails instead of returningWOLFCERT_ERR_MEMORY. Pre-existing, unrelated to this finding, and unfiled.