Add a maximum recursion depth to parse_script
What changed, and why it matters
This commit adds a safety limit to a recursive function called parse_script in Ledger's Bitcoin app. Without such a limit, an attacker could potentially feed the device an extremely nested Bitcoin script that causes the function to call itself so many times that the device's limited stack memory runs out. A stack overflow on a hardware wallet could crash the app or, in worst cases, be exploited to alter behavior. The fix caps nesting at 16 levels, which is above any realistic legitimate use.
Treat as a security hardening fix and include in release notes. Review whether other recursive parsers in the codebase (e.g., for key expressions or transaction parsing) need similar depth limits. Consider whether 16 is appropriate for the device's actual stack size and worst-case frame size.
Security signals we found
Unbounded recursion mitigated by explicit depth cap
Stack-exhaustion / denial-of-service class hardening
Input validation added to parser
No CVE, advisory, or researcher attribution present in commit
Evidence from the diff
The change introduces MAX_PARSE_SCRIPT_RECURSION_DEPTH (16) and a depth check at the top of parse_script() in src/common/wallet.c. If depth exceeds 16, it returns -1 with the message ‘Script is too deeply nested’. This prevents unbounded recursion when parsing deeply nested miniscript/policy descriptors (e.g., repeated sh()/wsh() wrappers). The comment notes the maximum observed test depth is 10, so 16 provides margin.
Changed components
src/common/wallet.cparse_script() functionLedger Bitcoin app wallet policy/descriptor parsingInspect captured patch +10 / −0
diff --git a/src/common/wallet.c b/src/common/wallet.c
index 0f75579..668ecb9 100644
--- a/src/common/wallet.c
+++ b/src/common/wallet.c
@@ -29,6 +29,12 @@ typedef struct {
const char *name;
} token_descriptor_t;
+// As parse_script is recursive, we set a maximum reasonable recursion depth in order to avoid the
+// risk of stack exhaustion.
+// At the time of writing, the maximum depth measured across all the tests is 10, so 16 still
+// leaves a margin for much more complex scripts and seems unlikely to be hit in practice.
+#define MAX_PARSE_SCRIPT_RECURSION_DEPTH 16
+
static const token_descriptor_t KNOWN_TOKENS[] = {
{.type = TOKEN_SH, .name = "sh"},
{.type = TOKEN_WSH, .name = "wsh"},
@@ -649,6 +655,10 @@ static int parse_script(buffer_t *in_buf,
int version,
size_t depth,
unsigned int context_flags) {
+ if (depth > MAX_PARSE_SCRIPT_RECURSION_DEPTH) {
+ return WITH_ERROR(-1, "Script is too deeply nested");
+ }
+
int n_wrappers = 0;
// Keep track of how many key expressions have been created while parsing
Why this scored 61/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.