psbt: fix check for too many witnesses
What changed, and why it matters
This commit fixes a parsing bug in how the library reads Bitcoin-style witness data from a transaction or PSBT. Before the fix, a malformed input could claim there were millions of witnesses while only providing a few bytes. The code would then try to read far beyond the available data, likely causing a crash or memory corruption. The fix adds a simple size check so the parser rejects obviously impossible inputs early.
Treat as a security fix and include in the next maintenance release. Backport to supported branches. The patch is minimal and only adds a lower-bound length check, so it is low-risk to deploy. Users parsing untrusted PSBTs or transactions should upgrade.
Security signals we found
Out-of-bounds read / buffer over-read in a core deserialization routine
Potential memory allocation failure or integer overflow from attacker-controlled witness count
New negative test case added for malformed PSBT with excessive witness count
Fix credited to external reporter @erickcestari
Evidence from the diff
In src/pullpush.c, pull_witness() deserializes a witness stack. It first pulls a varint num_witnesses from a bounded buffer (val/val_len), then allocates a witness stack of that size and loops reading each witness item. The pre-patch code did not verify that val_len was at least num_witnesses, even though each witness item must consume at least one byte (its own length prefix). A crafted input with a large num_witnesses and a small remaining buffer could therefore drive an out-of-bounds read or integer/heap overflow during allocation/iteration. The patch adds if (num_witnesses > val_len) return WALLY_EINVAL;, which is a conservative lower-bound sanity check. Test vectors for a ‘too many witnesses’ PSBT were also added to ctest/psbts.h and data/psbt.json.
Changed components
src/pullpush.c:pull_witness()PSBT parsing pathTransaction witness deserializationInspect captured patch +11 / −0
diff --git a/src/ctest/psbts.h b/src/ctest/psbts.h
index c477b7d..4a39074 100644
--- a/src/ctest/psbts.h
+++ b/src/ctest/psbts.h
@@ -361,6 +361,9 @@ static const struct psbt_test invalid_psbts[] =
/* PSBT (short bytes) */
{"cHNidP8BAPaccp32A/ZwcwD2/wAAAAAAAAAAAAAD9nBzAPb/AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAABAAAAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAAAAAAAAgAAAAAAAAAAAAAQAAAQAAAAEAAAABAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEhISEhASEhISEhXQqxjo6OEXD/t///AAAAYAAAAAAAAAAAAAA7ISEhISEhISEhIXBzAAAAABAREREREREREREAAAAAABER7u7u7gYGBgYGBvsG7gA=", false, true},
+ /* PSBT (too many witnesses) */
+ {"cHNidP8BAHBzYnT/AQBjAC8AAC0CA/79/fn9FBQUFAAAEAAAAAAlJSUBJSQlJSX//wAAAAAAAAAAAD0AAAcA+JL/////WgAAAAAAAQAAAAAAAAAAAAAAOgAKCgkKCgo0MHNic2JwdPtQAAD3AAAACAAAAAAAAAAAAHBzYnT/EABjAAABAAAAAAgICAgICAgICAgICAgJhISEhISEAAAAAAAAuwAAAExMPAAAAAoKISEhISEhDiEhISEhISEhISohISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISFwc2J0/wEIdP8BAGcAAAACAAkJTLIAAAAAAAAAAAAAAD0AAAcA+JL/////AAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAALgAAAAEAAAAAAAAAAAABBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAD0oAP/7AA==", false, true},
+
/* PSET Version 0 - not supported anymore */
{"cHNldP8BAOYCAAAAAAEJoA5k8iAABF66mJyk85LBsCUTVVfErOwxmexTjCBEyQAAAAAA/f///wMBXOe5Y9N/jy1Ryvu6koqqniILi7xmBXFJnANiijhRuM4BAAAAAO5rFFgAF6kUpruHAKCJvLhrCf5VkrhUvQ4+bDKHAVznuWPTf48tUcr7upKKqp4iC4u8ZgVxSZwDYoo4UbjOAQAAAAA7msoAABepFII2rTwCO4NdVWyoBFVzHVM/c1IBhwFc57lj03+PLVHK+7qSiqqeIguLvGYFcUmcA2KKOFG4zgEAAAAAAAATqAAAAAAAAAABAUMBXOe5Y9N/jy1Ryvu6koqqniILi7xmBXFJnANiijhRuM4BAAAAASoF8gAAF6kUIUgXxch89mjFY+aFBdmAk9d8GcmHAQQWABTs8/AAycufhZ2FR4uYndwqd1C7ogv8CGVsZW1lbnRzAAgA8gUqAQAAAAv8CGVsZW1lbnRzAiBc57lj03+PLVHK+7qSiqqeIguLvGYFcUmcA2KKOFG4zgABABYAFMFIn/H6YCLLR2ukTXG9evUf5T5VC/wIZWxlbWVudHMGIQMYlbtO8ZBg4wdFP1vS+d59msC7iEjmr/VATyVestonAwABABYAFJpBlksv6mEJcXOPlyskNv1O3yn+C/wIZWxlbWVudHMGIQLkIFPXDMnWVGXBPhDwpi5zMF/WmosNkQLltvC445AA2QAA", true, true},
diff --git a/src/data/psbt.json b/src/data/psbt.json
index 04e628a..5585945 100644
--- a/src/data/psbt.json
+++ b/src/data/psbt.json
@@ -472,6 +472,10 @@
"comment": "PSBT (short bytes)",
"psbt": "cHNidP8BAPaccp32A/ZwcwD2/wAAAAAAAAAAAAAD9nBzAPb/AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAABAAAAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAAAAAAAAgAAAAAAAAAAAAAQAAAQAAAAEAAAABAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEhISEhASEhISEhXQqxjo6OEXD/t///AAAAYAAAAAAAAAAAAAA7ISEhISEhISEhIXBzAAAAABAREREREREREREAAAAAABER7u7u7gYGBgYGBvsG7gA="
},
+ {
+ "comment": "PSBT (too many witnesses)",
+ "psbt": "cHNidP8BAHBzYnT/AQBjAC8AAC0CA/79/fn9FBQUFAAAEAAAAAAlJSUBJSQlJSX//wAAAAAAAAAAAD0AAAcA+JL/////WgAAAAAAAQAAAAAAAAAAAAAAOgAKCgkKCgo0MHNic2JwdPtQAAD3AAAACAAAAAAAAAAAAHBzYnT/EABjAAABAAAAAAgICAgICAgICAgICAgJhISEhISEAAAAAAAAuwAAAExMPAAAAAoKISEhISEhDiEhISEhISEhISohISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISFwc2J0/wEIdP8BAGcAAAACAAkJTLIAAAAAAAAAAAAAAD0AAAcA+JL/////AAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAALgAAAAEAAAAAAAAAAAABBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAD0oAP/7AA=="
+ },
{
"comment": "PSET Version 0 - not supported anymore",
"psbt": "cHNldP8BAOYCAAAAAAEJoA5k8iAABF66mJyk85LBsCUTVVfErOwxmexTjCBEyQAAAAAA/f///wMBXOe5Y9N/jy1Ryvu6koqqniILi7xmBXFJnANiijhRuM4BAAAAAO5rFFgAF6kUpruHAKCJvLhrCf5VkrhUvQ4+bDKHAVznuWPTf48tUcr7upKKqp4iC4u8ZgVxSZwDYoo4UbjOAQAAAAA7msoAABepFII2rTwCO4NdVWyoBFVzHVM/c1IBhwFc57lj03+PLVHK+7qSiqqeIguLvGYFcUmcA2KKOFG4zgEAAAAAAAATqAAAAAAAAAABAUMBXOe5Y9N/jy1Ryvu6koqqniILi7xmBXFJnANiijhRuM4BAAAAASoF8gAAF6kUIUgXxch89mjFY+aFBdmAk9d8GcmHAQQWABTs8/AAycufhZ2FR4uYndwqd1C7ogv8CGVsZW1lbnRzAAgA8gUqAQAAAAv8CGVsZW1lbnRzAiBc57lj03+PLVHK+7qSiqqeIguLvGYFcUmcA2KKOFG4zgABABYAFMFIn/H6YCLLR2ukTXG9evUf5T5VC/wIZWxlbWVudHMGIQMYlbtO8ZBg4wdFP1vS+d59msC7iEjmr/VATyVestonAwABABYAFJpBlksv6mEJcXOPlyskNv1O3yn+C/wIZWxlbWVudHMGIQLkIFPXDMnWVGXBPhDwpi5zMF/WmosNkQLltvC445AA2QAA",
diff --git a/src/pullpush.c b/src/pullpush.c
index b6e2361..0244373 100644
--- a/src/pullpush.c
+++ b/src/pullpush.c
@@ -256,6 +256,10 @@ int pull_witness(const unsigned char **cursor, size_t *max,
val_len = *max;
}
num_witnesses = pull_varint(&val, &val_len);
+ if (num_witnesses > val_len) {
+ /* Not enough bytes remaining for num_witnesses empty witnesses */
+ return WALLY_EINVAL;
+ }
ret = wally_tx_witness_stack_init_alloc(num_witnesses, witness_out);
for (i = 0; ret == WALLY_OK && i < num_witnesses; ++i) {
Why this scored 59/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.