fix: honour proof_out capacity and validate required pointers (Zellic DEFI-1108/1109/1110) - #147
Open
tesseract-ripple wants to merge 1 commit into
Open
tesseract-ripple wants to merge 1 commit into
tesseract-ripple wants to merge 1 commit into
Conversation
Resolves three Zellic scan findings against mpt-crypto@3baaaac.
DEFI-1110 (memory safety). secp256k1_bulletproof_prove_agg documents
*proof_len as the caller's buffer capacity on input, but overwrote it with
proof_size immediately after validating m. The only capacity check, in the
serialization block, then compared that value against a locally recomputed
`expected` equal to it, so `*proof_len < expected` was dead for every
supported m and an undersized proof_out was serialized past its end.
Reproduced under ASan on the unpatched tree: a 600-byte heap buffer with a
declared capacity of 600 takes a heap-buffer-overflow WRITE 0 bytes after the
region, in the trailing-scalar memcpy block, for an m=1 proof requiring 688
bytes. The same repro against this commit returns 0 and reports 688.
The capacity is now captured in a local before *proof_len is written, and the
size-query (proof_out == NULL), NULL-proof_len and insufficient-capacity paths
are all resolved at the top of the function, before any proving work rather
than after it. Both in-repo callers (mpt_get_bulletproof_agg via the Send and
ConvertBack builders) already pass kMPT_{SINGLE,DOUBLE}_BULLETPROOF_SIZE, which
equal the computed sizes for m=1 and m=2, so this is behaviour-compatible for
existing callers.
DEFI-1108 (robustness). secp256k1_bulletproof_create_commitment dereferenced
h_generator and commitment_C without validating them, so a NULL required
pointer crashed the process instead of returning the documented 0 (confirmed:
SIGSEGV on the unpatched tree). Added MPT_ARG_CHECK entry-point checks, the
idiom already used across elgamal.c and the proof modules. The same checks are
added to prove_agg, which had the same gap; context_id is deliberately excluded
as it is documented optional and NULL-guarded at every transcript absorption.
DEFI-1109 (documented, not changed). secp256k1_elgamal_add / _subtract cannot
represent an identity result, because a secp256k1_pubkey has no encoding for
the point at infinity and libsecp256k1 0.7.1's pubkey_combine returns 0 for it.
Introducing an identity-capable representation would change the exported
ciphertext representation that downstream consumers build on, so the
limitation is documented in the API contract instead and pinned by tests;
whether to change the representation is a protocol decision, not a library one. Both functions are WASM-exported for client-side
chained-balance prediction (CB_S' = CB_S -/+ the encrypted amount), which is the
path that can actually reach a cancellation, so the header now points callers at
generate_canonical_encrypted_zero() for a zero balance.
Adds tests/test_api_hardening.c. Verified as genuine regression tests against
the unpatched tree: the DEFI-1108 case exits 139 (SIGSEGV) and the DEFI-1110
case exits 134 (assertion), while the DEFI-1109 case passes both before and
after, since it pins existing behaviour rather than changing it.
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
Resolves the three findings from the Zellic scan of mpt-crypto@3baaaac (current
main): DEFI-1108, DEFI-1109, DEFI-1110 on the DEFI board. Each was verified against the source at that commit before anything was changed.DEFI-1110 (memory safety, the one that matters)
secp256k1_bulletproof_prove_aggdocuments*proof_lenas the caller's buffer capacity on input, but overwrites it withproof_sizeimmediately after validatingm. The only capacity check, in the serialization block, then compares that value against a locally recomputedexpectedequal to it, so*proof_len < expectedis dead for every supportedm, and an undersizedproof_outis serialized past its end.Reproduced under AddressSanitizer on unpatched
main: a 600-byte heap buffer with declared capacity 600 takes a heap-buffer-overflow WRITE 0 bytes after the region, in the trailing-scalar memcpy block, for an m=1 proof requiring 688 bytes (88-byte overrun). The same repro against this branch returns 0 and reports 688.Fix: capture the capacity in a local before
*proof_lenis written, and resolve the size-query (proof_out == NULL), NULL-proof_lenand insufficient-capacity paths at the top of the function, before any proving work rather than after it.Compatibility: both in-repo callers (
mpt_get_bulletproof_agg, reached from the Send and ConvertBack builders) already passkMPT_{SINGLE,DOUBLE}_BULLETPROOF_SIZE, which equal the computed sizes for m=1 and m=2, so existing callers are unaffected. Two behaviour differences worth knowing: a size query now returns immediately without running the prover (previously it returned 1 only if proving succeeded), and on an internal proving failure*proof_lenis left at the caller's capacity rather than set to the required size. Neither is relied on in-repo. Separately, note thatmpt_get_bulletproof_aggonly checks the written length after the call returns, so the utility layer was never itself enforcing the capacity it passes down; correct today because every caller hands it a matching constant.DEFI-1108 (robustness)
secp256k1_bulletproof_create_commitmentdereferencedh_generatorandcommitment_Cwithout validating them, so a NULL required pointer crashed the process instead of returning the documented 0 (confirmed: SIGSEGV on unpatchedmain). AddedMPT_ARG_CHECKentry-point checks, the idiom already used acrosselgamal.cand the proof modules. The same checks are added toprove_agg, which had the identical gap the scan did not flag.context_idis deliberately excluded: it is documented optional and NULL-guarded at every transcript absorption.DEFI-1109 (documented, deliberately not changed)
secp256k1_elgamal_add/_subtractcannot represent an identity result, because asecp256k1_pubkeyhas no encoding for the point at infinity; subtracting a ciphertext from itself, or any pair sharing a component, returns 0 indistinguishably from a genuine failure. Giving the ciphertext an identity-capable representation changes the exported representation downstream consumers build on, so this PR documents the limitation in the API contract and pins it with tests instead. Whether to change the representation is a protocol decision, not a library one, and it is the same underlying mechanism as AXRT-4218 at the transactor layer, so the two are worth deciding together. Both functions are WASM-exported specifically for client-side chained-balance prediction (CB_S' = CB_S -/+ the encrypted amount), which is the path that can actually reach a cancellation, so the header now points callers atgenerate_canonical_encrypted_zero()for a zero balance.Testing
New
tests/test_api_hardening.c. Verified to be genuine regression tests by building the same file against unpatchedmain: the DEFI-1108 case exits 139 (SIGSEGV) and the DEFI-1110 case exits 134 (assertion), while the DEFI-1109 case passes both before and after, since it pins existing behaviour rather than changing it.On this branch: Debug build on macOS with
MPT_CRYPTO_WERROR=ON, 13/13 ctest pass.pre-commit runclean on all changed files. The_sharedtest variant is exercised only in CI.