What changed, and why it matters
This commit adds a safety check to the Ledger Bitcoin app's transaction-signing code. Before signing a Bitcoin transaction, the app now refuses if the transaction has more outputs than a defined maximum. Without this check, a specially crafted transaction with an extremely large number of outputs could potentially overflow internal counters or exhaust memory, leading to crashes or unexpected behavior on the hardware wallet.
Review whether MAX_N_OUTPUTS_CAN_SIGN is consistent with all downstream array sizes and iteration limits, and confirm that other parsed counts in the same function (e.g., inputs) have equivalent bounds checks. Consider adding regression tests with oversized output counts.
Security signals we found
Missing upper-bound validation on parsed transaction output count
Integer-width transition from uint64_t to unsigned int without explicit range check
Potential memory exhaustion or buffer mishandling in output iteration
Defensive hardening patch in security-critical signing path
Evidence from the diff
In src/handler/sign_psbt.c, the function init_global_state() now validates n_outputs_u64 against MAX_N_OUTPUTS_CAN_SIGN before casting it to unsigned int and storing it in st->n_outputs. Previously, the code only checked that the encoded output count fit in 8 bytes (n_outputs_u64 <= 0xFFFFFFFF). A malicious or malformed PSBT could supply a value larger than MAX_N_OUTPUTS_CAN_SIGN, which after the cast could still be within unsigned int range but cause downstream loops or buffer operations to misbehave. The patch rejects such inputs with SW_NOT_SUPPORTED.
Changed components
src/handler/sign_psbt.cPSBT signing flowTransaction output parsingInspect captured patch +5 / −0
diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c
index 9956d53..6908296 100644
--- a/src/handler/sign_psbt.c
+++ b/src/handler/sign_psbt.c
@@ -235,6 +235,11 @@ init_global_state(dispatcher_context_t *dc, sign_psbt_state_t *st) {
SEND_SW(dc, SW_WRONG_DATA_LENGTH);
return false;
}
+ if (n_outputs_u64 > MAX_N_OUTPUTS_CAN_SIGN) {
+ PRINTF("At most %d outputs are supported\n", MAX_N_OUTPUTS_CAN_SIGN);
+ SEND_SW(dc, SW_NOT_SUPPORTED);
+ return false;
+ }
st->n_outputs = (unsigned int) n_outputs_u64;
uint8_t wallet_hmac[32];
Why this scored 58/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.