Repository navigation
jose: Fix NULL pointer dereference and OOB read in compact JWE decoding - #74
Open
kunwaratulhax0r wants to merge 1 commit into
Open
kunwaratulhax0r wants to merge 1 commit into
kunwaratulhax0r wants to merge 1 commit into
Conversation
apr_jose_jwe_make() dereferenced its payload argument unconditionally (jose->cty = payload->cty) even though payload is documented and declared (via __attribute__((nonnull(6))), which excludes the payload parameter) as optional. apr_jose_decode_compact_jwe() always calls it with payload == NULL, since no payload exists yet at that point in decryption. This means every call to apr_jose_decode() on a JWE in Compact Serialization crashes with a NULL pointer dereference, for any well-formed input, not just malformed input. The sibling constructors (apr_jose_jws_make, apr_jose_jwe_json_make, apr_jose_jws_json_make) all correctly guard the same line with `if (payload)`; apr_jose_jwe_make was the only one missing it. Separately, in apr_jose_decode_compact_jwe(), the JWE tag segment's length was computed as `dot - left`, where `dot` is the delimiter found for the *previous* (ciphertext) segment and `left` was just set to `dot + 1` on the line above -- so this expression is always exactly -1. Passed as the signed slen argument to apr_pdecode_base64_binary(), -1 is APR_ENCODE_STRING, which tells the decoder to scan for a NUL terminator instead of respecting a length, so it can read past the end of the real input buffer. The tag is the last segment in Compact Serialization (no trailing delimiter), so its length should be measured against the end of the input, `right`, not against the previous segment's delimiter. This was found and confirmed with a standalone reproduction: calling apr_jose_decode() with a real decrypt callback on a well-formed JWE compact string crashed before reaching the callback; after fixing the first bug, the decrypt callback was reached but reported a tag length that read past the intended segment into adjacent memory; after also fixing the second, the reported tag length matched the actual encoded tag exactly. Neither released APR (<= 1.7.6) nor apr-util (<= 1.6.5) ships the jose module; it exists only on unreleased development branches.
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.
Summary
Two bugs in Compact Serialization JWE decoding, both in
jose/apr_jose_decode.c/jose/apr_jose.c:1. NULL pointer dereference in
apr_jose_jwe_make()(jose/apr_jose.c)payloadis dereferenced unconditionally, but it's documented and declared as optional — the function's own__attribute__((nonnull(6)))covers only thepoolparameter, deliberately excludingpayload.apr_jose_decode_compact_jwe()always calls this withpayload == NULL(no payload object exists yet at that point in decryption), so every call toapr_jose_decode()on a JWE in Compact Serialization crashes, for any well-formed input, not just malformed input.The sibling constructors
apr_jose_jws_make,apr_jose_jwe_json_make, andapr_jose_jws_json_makeall correctly guard the identical line withif (payload) { ... }.apr_jose_jwe_makeis the only one of the four missing it.2. Out-of-bounds read in
apr_jose_decode_compact_jwe()(jose/apr_jose_decode.c)dotis the delimiter found for the previous (ciphertext) segment, andleftwas just set todot + 1on the line above — sodot - leftis always exactly-1. Passed as the signedslenargument toapr_pdecode_base64_binary(),-1isAPR_ENCODE_STRING, which tells the decoder to scan for a NUL terminator instead of respecting a length, reading past the end of the real input buffer. The tag is the last segment in Compact Serialization (no trailing delimiter), so its length should be measured against the end of the input,right, not against the previous segment's delimiter.This second bug was masked by the first — the NULL deref happens earlier in the same call path, so the OOB read was never actually reached until bug 1 was fixed.
Verification
Built
apache/aprtrunk from source and reproduced both issues with a standalone test callingapr_jose_decode()with a realdecryptcallback on a well-formed Compact JWE string:SIGSEGV) insideapr_jose_jwe_make, called fromapr_jose_decode_compact_jwe, before the decrypt callback is ever reached.Scope note
Neither released APR (≤ 1.7.6) nor apr-util (≤ 1.6.5) ships the
josemodule — it exists only on unreleased development branches (APR 2.0.0-dev on trunk, and an apr-util1.7.xbranch that has never had a tagged release). This is a pre-release bug fix, not a report against any shipped version.Test plan
apr_josetest suite (if any) — please let me know if there's a specific test target to run