From 4e3eb5847791c603afe0c2747edc8616792c1478 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 24 Jul 2026 10:53:29 -0700 Subject: [PATCH] lightningd: fail sendpay cleanly when the route does not fit the onion create_onionpacket returns NULL when the route's per-hop payloads exceed the 1300-byte onion; send_payment passed the packet to send_onion unchecked, and serialize_onionpacket dereferenced it, killing lightningd with SIGSEGV. Observed in production on a 25-hop route submitted by a rebalancing plugin. The sendonion path already checks this call and fails the command; mirror it, and add a test. Changelog-Fixed: JSON-RPC: `sendpay` with a route too long to fit the onion packet now fails cleanly instead of crashing lightningd. --- lightningd/pay.c | 3 +++ tests/test_pay.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lightningd/pay.c b/lightningd/pay.c index 1abf7585a7e1..e74d275333a1 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 0f357547ae76..1fdfa5e661ed 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):