psbt: fix check for short buffers
What changed, and why it matters
This commit fixes a bug in how the library reads compact size numbers from PSBT (Partially Signed Bitcoin Transaction) data. Before the fix, the code could read past the end of a short input buffer when checking how many bytes a variable-length integer needs. The fix adds a check that at least one byte is available before asking how long the integer is. This is a memory-safety bug that could cause crashes or undefined behavior when parsing malformed PSBT data.
Treat as a security-relevant memory-safety fix. Review whether the same pattern exists elsewhere in the codebase, backport to supported branches, and run the new short-bytes test vectors through fuzzing/ASan to confirm no further out-of-bounds reads.
Security signals we found
Out-of-bounds read in variable-length integer parsing
Short/truncated input buffer not validated before length inspection
New negative test case added for short PSBT bytes
Memory safety issue in PSBT deserialization path
Fix credited to external reporter @erickcestari
Evidence from the diff
In src/transaction.c, the ensure_varint macro previously called varint_length_from_bytes(p) inside ensure_n() without first verifying that p was at least one byte before end. For a short or truncated buffer, varint_length_from_bytes could dereference one or more bytes out of bounds to determine the varint’s length, leading to an out-of-bounds read. The patch splits the check: first ensure at least sizeof(uint8_t) (one byte) is available, then ensure the full varint length is available, before decoding. New test vectors labeled “PSBT (short bytes)” are added to both psbts.h and psbt.json to exercise this case.
Changed components
src/transaction.cPSBT parsing/deserializationvarint decoding macros (ensure_varint)ctest PSBT test vectors (src/ctest/psbts.h)JSON PSBT test vectors (src/data/psbt.json)Inspect captured patch +9 / −1
diff --git a/src/ctest/psbts.h b/src/ctest/psbts.h
index a4d802a..c477b7d 100644
--- a/src/ctest/psbts.h
+++ b/src/ctest/psbts.h
@@ -358,6 +358,9 @@ static const struct psbt_test invalid_psbts[] =
/* PSBTv2 with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME greater than or equal to 500000000 */
{"cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARIEAGXNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", false, true},
+ /* PSBT (short bytes) */
+ {"cHNidP8BAPaccp32A/ZwcwD2/wAAAAAAAAAAAAAD9nBzAPb/AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAABAAAAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAAAAAAAAgAAAAAAAAAAAAAQAAAQAAAAEAAAABAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEhISEhASEhISEhXQqxjo6OEXD/t///AAAAYAAAAAAAAAAAAAA7ISEhISEhISEhIXBzAAAAABAREREREREREREAAAAAABER7u7u7gYGBgYGBvsG7gA=", 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 b852ad7..04e628a 100644
--- a/src/data/psbt.json
+++ b/src/data/psbt.json
@@ -468,6 +468,10 @@
"comment": "PSBTv2 with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME greater than or equal to 500000000",
"psbt": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARIEAGXNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="
},
+ {
+ "comment": "PSBT (short bytes)",
+ "psbt": "cHNidP8BAPaccp32A/ZwcwD2/wAAAAAAAAAAAAAD9nBzAPb/AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAABAAAAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAAAAAAAAgAAAAAAAAAAAAAQAAAQAAAAEAAAABAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEhISEhASEhISEhXQqxjo6OEXD/t///AAAAYAAAAAAAAAAAAAA7ISEhISEhISEhIXBzAAAAABAREREREREREREAAAAAABER7u7u7gYGBgYGBvsG7gA="
+ },
{
"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/transaction.c b/src/transaction.c
index b5e6e27..4d191f4 100644
--- a/src/transaction.c
+++ b/src/transaction.c
@@ -2235,7 +2235,8 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len,
#define ensure_n(n) if ((n) > (size_t)(end - p)) return WALLY_EINVAL
-#define ensure_varint(dst) ensure_n(varint_length_from_bytes(p)); \
+#define ensure_varint(dst) ensure_n(sizeof(uint8_t)); \
+ ensure_n(varint_length_from_bytes(p)); \
p += varint_from_bytes(p, (dst))
#define ensure_varbuff(dst) ensure_varint((dst)); \
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.