From aba798d667c61ea32afef827e66a4616ed0a7513 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Thu, 19 Jun 2025 08:08:46 +0000 Subject: [PATCH 1/2] common/bolt12: fix `tlv_span()` behaviour with empty `tlvstream` Changelog-Fixed: When `tlv_span()` receives an empty `tlvsteam`, it sets the value of `startp` to `start` - `tlvstream` = `NULL` - `tlvstream` where `tlvstream` is a pointer. Similarly, `end` can also become `NULL` when `fromwire_bigsize()` reads to the end of `cursor` and then sets `cursor` = `NULL` resulting in the same error. Since this is undefined behaviour, add a fix for it by correcting the initial value of `end` and guarding against `cursor` = `NULL`. --- common/bolt12.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/common/bolt12.c b/common/bolt12.c index a0c8c9f084c5..822b3fa1e040 100644 --- a/common/bolt12.c +++ b/common/bolt12.c @@ -613,16 +613,26 @@ size_t tlv_span(const u8 *tlvstream, u64 minfield, u64 maxfield, size_t tlvlen = tal_bytelen(tlvstream); const u8 *start, *end; - start = end = NULL; + start = NULL; + end = tlvstream; while (tlvlen) { const u8 *before = cursor; bigsize_t type = fromwire_bigsize(&cursor, &tlvlen); + if (!cursor) + break; + bigsize_t len = fromwire_bigsize(&cursor, &tlvlen); + if (!cursor) + break; + if (type >= minfield && start == NULL) start = before; if (type > maxfield) break; fromwire_pad(&cursor, &tlvlen, len); + if (!cursor) + break; + end = cursor; } if (!start) From 39ac5a87cdfb3c8a23fbade0710cbcece38e1df1 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Thu, 19 Jun 2025 08:04:22 +0000 Subject: [PATCH 2/2] common/test: Add a test to trigger the undefined behaviour Add a test that reproduces the UB when the fix is not applied. --- common/test/run-tlv_span.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/test/run-tlv_span.c b/common/test/run-tlv_span.c index 4055ec9bcaae..97c124a2a4ab 100644 --- a/common/test/run-tlv_span.c +++ b/common/test/run-tlv_span.c @@ -131,6 +131,22 @@ int main(int argc, char *argv[]) len = tlv_span(wire, 0, 1, &start); assert(start == 0); assert(len == strlen("0010b8538094dbd70d8a0f0439d8e64f766f") / 2); + + /* Simulate an empty tlvstream */ + wire = tal_arr(tmpctx, u8, 0); + len = tlv_span(wire, 0, UINT64_MAX, &start); + assert(start == 0); + assert(len == 0); + + /* Simulate a TLV stream where the payload is shorter + * than its length field indicates. + */ + wire = tal_hexdata(tmpctx, "0502beef0a03de", strlen("0502beef0a03de")); + len = tlv_span(wire, 0, UINT64_MAX, &start); + assert(start == 0); + /* The span should cover only the first valid record, which is 4 bytes long. */ + assert(len == 4); + common_shutdown(); return 0; }