Skip to content

offers, xpay, hsmd, gossipd: assorted fixes (v26.06.7 security, 7/7) - #9513

Merged
nGoline merged 9 commits into
ElementsProject:masterfrom
nGoline:port-26.06.7-misc
Sep 15, 2026
Merged

nGoline merged 9 commits into
ElementsProject:masterfrom
nGoline:port-26.06.7-misc

Conversation

@nGoline

@nGoline nGoline commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Last of seven PRs forward-porting the v26.06.7 security point release onto master. Independent fixes that do not group with the others.

9 commits:

  • xpay: quote BOLT #12 requirements in the offer payment path.
  • xpay: test that a fetched invoice's amount matches what we asked for.
  • xpay: honour the BOLT #12 invoice_amount check.
  • hsmd: return io_close when the handler reports an error
  • hsmd: test that a NULL reply closes the client exactly once
  • offers: treat an unparseable reply path as absent, not fatal
  • pytest: onion message reply path with no hops must not stop the node
  • gossipd: don't bother looking up ancient blocks.
  • lightningd: prefer latest blocks when querying historic blocks.

Three Changelog-Fixed: offers no longer stops the node when an onion message carries a reply path with no hops; xpay checks a fetched bolt12 invoice's amount is the one we asked for; and "bad commit secret" prints the commitment index rather than a stack address.

For reviewers

Adds hsmd/test/Makefile and hsmd/test/run-bad-request-close.c, a new unit test directory for hsmd.

Applies cleanly to master with no conflicts, and every commit is byte-identical to its release-line original.

nGoline and others added 9 commits September 15, 2026 18:35
… path.

We check the offer and the fetched invoice in several places without
saying which requirement each check comes from, so it's hard to tell
what we implement and what we merely assume.  Add the quotes and let
check-source-bolt keep them current.

Changelog-None: comments only.
(cherry picked from commit 07734f7)
invoice_amount sits outside the TLV ranges an invoice must copy from
our invoice_request, so the payee sets it independently of the amount
in our request.

(cherry picked from commit 808ad86)
We sent invreq_amount, so the invoice must have an equal invoice_amount;
we passed msat=NULL into xpay_core() and used the invoice's own figure
instead.  The default fee cap scales off that same figure.

Carry the amount we asked for through to invoice_fetched() and check it.

Changelog-Fixed: xpay: check a fetched bolt12 invoice's amount is the one we asked for.
(cherry picked from commit 51c9a1f)
When libhsmd rejects a request it returns NULL; close the client then,
instead of from hsmd_status_bad_request. Share the report path in
report_bad_req so it cannot drift from bad_req_fmt.

Changelog-Fixed: print the commitment index in "bad commit secret" instead of a stack address
(cherry picked from commit 869bd4d)
libhsmd returning NULL has no black-box behaviour change, so drive
hsmd.c's io loop directly: io_set_finish counts the close, and a
write after close aborts.

Changelog-None

(cherry picked from commit 523be99)
A remote peer supplies the reply path of an onion message, and its
blinded hop count is a single unbounded byte. A zero-hop path parses to
an empty hop array, which json_to_blinded_path rejects with NULL;
offers then called plugin_err, and as an important builtin its death
takes lightningd down with it.

Drop a hopless reply path in lightningd when the message is decoded, and
in offers log and ignore any reply path that fails to parse rather than
aborting: a value that arrived from a remote peer must never terminate
the plugin.

Changelog-Fixed: plugins: `offers` no longer stops the node when an onion message carries a reply path with no hops.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5fe925d)
Regression test: inject an onion message whose blinded reply path
declares num_hops=0 and check the node processes it and stays up.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5580b1b)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
(cherry picked from commit 511106d)
Worst case, we can get flooded with 10,000 scids, and we work through
them one at a time.  In practice, there are more channels in more
recent blocks, so we should bias towards those.  The iteration is not
free, but compared to all those getutxo calls with each block, it's
invisible.

```
$ ./devtools/dump-gossipstore gossip_store-2026-08-17 | grep -v ' t=0 ' | sed -n 's/.*channel_announcement(\([0-9]*\)x.*).*/\1/p' | stats --histogram
962804************************************************************************
|********************************************************
|**********************************
|***************
|**********
|**********
|********
|*******
|********
|***
|*
|*
|***
515419
```

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>

(cherry picked from commit cf06f3d)
@nGoline
nGoline merged commit 3588f82 into ElementsProject:master Sep 15, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants