lightningd: fail sendpay cleanly when the route does not fit the onion
What changed, and why it matters
This commit fixes a crash bug in Core Lightning's payment command. When a user or plugin submitted a payment route with too many hops to fit inside the cryptographic 'onion' envelope, the software failed to check whether the onion was actually created and then tried to use a non-existent object, causing the entire lightningd process to crash with a segmentation fault (SIGSEGV). The patch adds a simple missing check so the command now returns a clean error message instead of crashing the node.
Apply the patch and run the included regression test. Review other callers of create_onionpacket() for missing NULL checks. Consider adding an upper-bound validation on route length earlier in sendpay parsing to fail faster with a clearer message.
Security signals we found
NULL-pointer dereference leading to daemon crash (SIGSEGV)
Missing return-value check on create_onionpacket()
Denial-of-service vector: malformed/long route crashes lightningd
Crash observed in production on a 25-hop rebalancing-plugin route
Existing sendonion path already had correct check, indicating this path was overlooked
Evidence from the diff
In lightningd/pay.c, send_payment() called create_onionpacket() and passed the resulting packet directly to send_payment_core() / serialize_onionpacket() without checking for NULL. create_onionpacket() returns NULL when per-hop payloads exceed the 1300-byte onion limit. This caused a NULL-pointer dereference/SIGSEGV. The patch mirrors the existing check in the sendonion path: if packet is NULL, it returns command_fail() with ‘Could not create onion packet’. A regression test in tests/test_pay.py constructs a 30-hop route and verifies the RPC now fails cleanly with that message.
Changed components
lightningd/pay.c send_payment()create_onionpacket() callers in payment pathJSON-RPC sendpay commandInspect captured patch +30 / −0
diff --git a/lightningd/pay.c b/lightningd/pay.c
index 1abf7585..e74d2753 100644
--- a/lightningd/pay.c
+++ b/lightningd/pay.c
@@ -1259,6 +1259,9 @@ send_payment(struct lightningd *ld,
channels[i] = route[i].scid;
packet = create_onionpacket(tmpctx, path, ROUTING_INFO_SIZE, &path_secrets);
+ if (!packet)
+ return command_fail(cmd, LIGHTNINGD,
+ "Could not create onion packet");
return send_payment_core(ld, cmd, rhash, partid, group, &route[0],
msat, total_msat,
label, invstring, description,
diff --git a/tests/test_pay.py b/tests/test_pay.py
index 04786bc5..3ca2493d 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -691,6 +691,33 @@ def test_wait_sendpay(node_factory, executor):
l1.rpc.waitsendpay(inv['payment_hash'])['payment_preimage']
+def test_sendpay_onion_overflow(node_factory):
+ """A route whose per-hop payloads exceed the 1300-byte onion must
+ fail cleanly: create_onionpacket returns NULL and send_payment
+ used it unchecked, crashing lightningd (SIGSEGV in
+ serialize_onionpacket)."""
+ l1, l2 = node_factory.line_graph(2, fundamount=10**6)
+
+ amt = 1000
+ inv = l2.rpc.invoice(amt, 'onionoverflow', 'desc')
+
+ # Each TLV hop costs ~50 onion bytes at these amounts; 30 hops
+ # cannot fit in the 1300-byte onion no matter how small the
+ # encodings. Only the first hop must be a live channel: the
+ # onion is built before anything is sent.
+ hop = {
+ 'amount_msat': amt,
+ 'id': l2.info['id'],
+ 'delay': 5,
+ 'channel': first_scid(l1, l2)
+ }
+ route = [copy.deepcopy(hop) for _ in range(30)]
+
+ with pytest.raises(RpcError, match='Could not create onion packet'):
+ l1.rpc.sendpay(route, inv['payment_hash'],
+ payment_secret=inv['payment_secret'])
+
+
@unittest.skipIf(TEST_NETWORK != 'regtest', "The reserve computation is bitcoin specific")
@pytest.mark.parametrize("anchors", [False, True])
def test_sendpay_cant_afford(node_factory, anchors):
Why this scored 66/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.