lightningd: handle unparsable onion from first hop when using injectonionmessage.
What changed, and why it matters
This fix stops Core Lightning from crashing with a fatal signal 11 (segfault) when a payment sent via the low-level `sendonion`/`injectpaymentonion` path fails because the very first peer reports a malformed onion. Before the fix, the code could call an internal failure-handling routine with both the failure message and the failure onion set to NULL, which is not allowed and caused the daemon to crash. The patch reorders the logic so the malformed-onion message is used when available, and adds a safety assertion that the onion is present in the remaining case.
Apply the patch and run the now-enabled `test_bad_onion_immediate_peer` regression test. Node operators using `sendonion`/`injectpaymentonion` should upgrade to a release containing this fix to avoid denial-of-service crashes from malformed first-hop onions.
Security signals we found
NULL pointer dereference / segmentation fault in payment failure path
Crash triggered by malformed-onion report from first hop
Logic branch ordering allowed both failmsg and failonion to be NULL simultaneously
Fix reorders conditional checks and adds an assertion for the remaining invariant
Previously failing test is now enabled (xfail removed)
Evidence from the diff
In lightningd/pay.c::payment_failed(), the branch for payment->path_secrets == NULL (used by sendonion/injectonionmessage) was evaluated before the failmsg branch. When the immediate peer rejects the onion via update_fail_malformed_htlc, failmsg is set but failonion is NULL. The original code first entered the path_secrets == NULL branch, setting fail = NULL and failstr = NULL, then later called injectonion_fail() with both failmsg and failonion NULL, triggering a NULL dereference/segfault. The patch moves the failmsg branch ahead of the path_secrets == NULL branch, so the malformed-HTLC message is consumed first, and adds assert(failonion != NULL) in the path_secrets == NULL branch to document and enforce the invariant. The test test_bad_onion_immediate_peer is no longer marked as expected to fail.
Changed components
lightningd/pay.ctests/test_misc.pysendonion / injectpaymentonion RPC flowsHTLC failure handling (payment_failed, injectonion_fail)Inspect captured patch +8 / −8
diff --git a/lightningd/pay.c b/lightningd/pay.c
index 44931d70..bd85e732 100644
--- a/lightningd/pay.c
+++ b/lightningd/pay.c
@@ -588,13 +588,6 @@ void payment_failed(struct lightningd *ld,
onion_wire_name(fail->failcode));
failstr = localfail;
pay_errcode = PAY_TRY_OTHER_ROUTE;
- } else if (payment->path_secrets == NULL) {
- /* This was a payment initiated with `sendonion`/`injectonionmessage`, we therefore
- * don't have the path secrets and cannot decode the error
- * onion. We hand it to the user. */
- pay_errcode = PAY_UNPARSEABLE_ONION;
- fail = NULL;
- failstr = NULL;
} else if (failmsg) {
/* This can happen when a direct peer told channeld it's a
* malformed onion using update_fail_malformed_htlc. */
@@ -602,6 +595,14 @@ void payment_failed(struct lightningd *ld,
origin_index = 0;
pay_errcode = PAY_TRY_OTHER_ROUTE;
goto use_failmsg;
+ } else if (payment->path_secrets == NULL) {
+ /* This was a payment initiated with `sendonion`/`injectonionmessage`, we therefore
+ * don't have the path secrets and cannot decode the error
+ * onion. We hand it to the user. */
+ assert(failonion != NULL);
+ pay_errcode = PAY_UNPARSEABLE_ONION;
+ fail = NULL;
+ failstr = NULL;
} else {
/* Must be normal remote fail with an onion-wrapped error. */
failstr = "reply from remote";
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 4508cf0f..a4d355b1 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -2119,7 +2119,6 @@ def test_bad_onion(node_factory, bitcoind):
assert err.value.error['data']['erring_channel'] == route[1]['channel']
-@pytest.mark.xfail(strict=True)
def test_bad_onion_immediate_peer(node_factory, bitcoind):
"""Test that we handle the malformed msg when we're the origin"""
l1, l2 = node_factory.line_graph(2, opts=[{}, {'dev-fail-process-onionpacket': None}])
Why this scored 64/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.