Fix Lightning invoice parsing and fetching

Three issues were causing invoices to not render or fetch:

1. bech32.c: Hardcoded MAX_PREFIX limited HRP length, but BOLT11 HRPs
   can be arbitrarily long depending on amount. Now derives max HRP
   length dynamically from input length (len-6 to match bolt11.c buffer).

2. content_parser.c: bolt11_decode_minimal was passed a pointer into
   the content buffer without null-termination. When a note contained
   multiple invoices, the decoder would read past the first invoice
   into newlines and the second invoice, causing checksum failure.
   Fixed by creating a null-terminated copy using strndup.

3. bolt11.c: bech32_decode_alloc allocated buffers using strlen(str)-6
   and strlen(str)-8 without checking minimum length first. For inputs
   shorter than 8 chars, this caused size_t underflow leading to huge
   allocations and potential crash. Added early length guard.

IMPORTANT: bech32_decode callers must allocate hrp buffer of at least
strlen(input) - 6 bytes. This matches existing bolt11.c usage.

Changelog-Fixed: Fixed Lightning invoice parsing and fetching for all amounts

Closes: https://github.com/damus-io/damus/issues/3456
Closes: https://github.com/damus-io/damus/issues/3151
Signed-off-by: alltheseas <alltheseas@noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
alltheseas
2026-02-02 09:24:22 -06:00
committed by Daniel D’Aquino
parent c88d881801
commit 845089bed1
3 changed files with 28 additions and 5 deletions

View File

@@ -157,13 +157,21 @@ bech32_encoding bech32_decode_len(char* hrp, uint8_t *data, size_t *data_len, co
}
}
/**
* Decode a bech32 string.
*
* IMPORTANT: Caller must allocate hrp buffer of at least (strlen(input) - 6) bytes
* to hold the maximum possible HRP for the given input. This matches bolt11.c usage.
*/
bech32_encoding bech32_decode(char* hrp, uint8_t *data, size_t *data_len, const char *input, size_t max_input_len) {
size_t len = strlen(input);
if (len > max_input_len) {
return BECH32_ENCODING_NONE;
}
static const int MAX_PREFIX = 10; // 9 bytes for the text, 1 byte for the null terminator
return bech32_decode_len(hrp, data, data_len, input, len, MAX_PREFIX);
// Derive max HRP length from input: input = hrp + '1' + data + checksum(6)
// Caller must allocate hrp buffer of at least len-6 bytes (see bolt11.c:386)
int max_hrp_len = (len > 6) ? (int)(len - 6) : 1;
return bech32_decode_len(hrp, data, data_len, input, len, max_hrp_len);
}
int bech32_convert_bits(uint8_t* out, size_t* outlen, int outbits, const uint8_t* in, size_t inlen, int inbits, int pad) {

View File

@@ -376,8 +376,15 @@ static bool bech32_decode_alloc(const tal_t *ctx,
size_t *data_len,
const char *str)
{
char *hrp = tal_arr(ctx, char, strlen(str) - 6);
u5 *data = tal_arr(ctx, u5, strlen(str) - 8);
size_t len = strlen(str);
/* Minimum: 1 HRP + '1' separator + 6 checksum = 8 chars.
* Guard prevents underflow in (len - 6) and (len - 8) below. */
if (len < 8)
return false;
char *hrp = tal_arr(ctx, char, len - 6);
u5 *data = tal_arr(ctx, u5, len - 8);
if (bech32_decode(hrp, data, data_len, str, (size_t)-1)
!= BECH32_ENCODING_BECH32) {

View File

@@ -180,7 +180,15 @@ static int push_invoice_str(struct ndb_content_parser *p, struct ndb_str_block *
struct bolt11 *bolt11;
char *fail;
if (!(bolt11 = bolt11_decode_minimal(NULL, str->str, &fail)))
// Create null-terminated copy since str->str points into content buffer
char *invoice_str = strndup(str->str, str->len);
if (!invoice_str)
return 0;
bolt11 = bolt11_decode_minimal(NULL, invoice_str, &fail);
free(invoice_str);
if (!bolt11)
return 0;
start = p->buffer.p;