Summary
This post details two distinct Denial-of-Service (DoS) vulnerabilities discovered in Core Lightning (CLN). Both vulnerabilities allowed a remote peer to trigger unbounded memory growth, ultimately leading to Out-of-Memory (OOM) system crashes.
The first vulnerability affected the connectd daemon and was patched in release v26.04. The second, related vulnerability affected the gossipd daemon and has been patched in release v26.06rc2. Both vulnerabilities stem from work initiated during my Summer of Bitcoin 2025 internship. All node operators are strongly advised to ensure they are running the latest patched versions.
Background
Core Lightning utilizes a multi-daemon architecture designed to isolate faults. The Lightning Network relies on a “gossip” protocol to propagate network state, including channel announcements, channel updates, and node announcements.
In CLN’s architecture, the connect daemon (connectd) receives external messages from peers and passes them to the gossip daemon (gossipd), which is responsible for managing the global view of the network. Because the network is inherently noisy, both daemons must operate with high efficiency. Furthermore, since they process untrusted external inputs, they must be robust against malicious message floods.
Vulnerability 1: connectd Message Queue Exhaustion
Discovery
This initial vulnerability was discovered using a new fuzz target I developed during my SoB internship, fuzz-gossipd-connectd, which was aimed at testing the robustness of gossipd’s state machines.
Here is a brief overview of how the fuzz target works (see the corresponding Pull Request for full details):
/* 0 - In each fuzz run, perform the following steps: */
void run(const u8* data, size_t size) {
/* 1 - Mock the required setup for connectd-gossipd communication. */
initialize_setup(data, size);
/* 2 - State loop. Repeat until there's no more fuzzer data left. */
while (size) {
/* 3 - Use a fuzzer byte as decision variable to perform one of the three possible operations: */
switch (consume_int(data) % OP_COUNT) {
case NEW PEER:
/* 3.1 - Register a fake peer to attribute messages to. */
connectd_new_peer(daemon, create_random_peer_id());
case RECV GOSSIP:
/* 3.2.1 - Generate a cryptographically valid gossip. Can be one of: */
/* - Channel Announcements */
/* - Channel Updates */
/* - Node Announcements */
/* - Reply channel_range */
/* - Reply short_channel_ids_end */
msg = create_gossip_msg(data, size, random_peer());
/* 3.2.2 - Parse the gossip created. */
handle_recv_gossip(daemon, msg);
case PEER GONE:
/* 3.3 - Delete a peer from the network map. */
connectd_peer_gone(daemon, remove_random_peer());
}
}
The Bug
A remote attacker could trigger a DoS condition in the connectd sub-daemon by flooding it with a high volume of channel_update gossip messages. The root cause was a resource management issue in the inter-daemon message queue shared between connectd and gossipd.
do_enqueue() allocated a new copy of every incoming message. Under a high-volume message flood, messages were enqueued far faster than they could be dequeued and processed, causing the internal queue to grow without bounds. While a warning for excessive queue length (exceeding 250,000 items) existed, it was suppressed after the first instance due to a warned_once flag. This left no further indication of the ongoing memory consumption as the queue swelled into the millions of messages, ultimately consuming all available RAM and triggering swap death.
Vulnerability 2: gossipd Unknown SCID Map Exhaustion
Discovery
Ironically, this second vulnerability was discovered while I was verifying the fix for the connectd bug described above.
The Bug
Similar to the first bug, a remote attacker could trigger a DoS condition by flooding the node with a high volume of channel_update messages. However, in this case, the unbounded memory growth occurred in gossipd’s internal map for tracking unknown short channel IDs (SCIDs): unknown_scids.
When processing incoming updates via handle_recv_gossip(), gossipd assumes that if an SCID is unrecognized, it missed the original announcement and passes the data to query_unknown_channel(). To remember to query peers for these missing channels, the daemon calls add_unknown_scid(), which inserts every unique, fake SCID into an internal integer map (uintmap).
A continuous stream of unique, bogus SCIDs caused this uintmap to continuously allocate memory to split its internal nodes (split_node()). This caused gossipd’s memory consumption to balloon infinitely without logging any warnings.
Verification & Execution
Both vulnerabilities are triggerable using the exact same attack program. The program acts as a malicious Lightning Network peer that connects to a CLN node and orchestrates a message flood to trigger the crash:
-
Initialization and Connection: The attacker establishes a connection to the victim CLN node.
-
Initial Handshake: The attacker completes the standard
initmessage handshake with the victim. -
The Malicious Flood: The attacker begins sending a continuous, high-volume stream of
channel_updategossip messages to the victim node. -
Unbounded Growth and Crash: Depending on the unpatched vulnerability present, either
connectdqueues the messages until memory is exhausted, orgossipdprocesses the bogus SCIDs and continuously expands itsuintmapuntil all physical RAM is depleted, resulting in a system freeze.
Criticality
| Severity | Medium/High (DoS) |
| Attack Vector | Remote (Peer-to-Peer) |
| Consequence | A remote attacker can reliably crash any publicly accessible CLN node by flooding it with peer-to-peer traffic. This requires no on-chain funds or channel relationship, making it a potent vector for disrupting network liquidity. |
The Fixes
The architectural reasoning behind caching channel_update messages internally is to avoid losing gossip data for a channel that hasn’t been discovered yet. This allows the node to reach the latest state of a channel much faster when the corresponding channel_announcement eventually arrives, eliminating the need for the peer to rebroadcast the gossip. However, unbounded caching creates DoS vectors.
Patch 1: connectd Message Queue
The optimal fix for the connectd queue was to simply drop messages after a specific cutoff point, which was decided to be 500,000 messages. More details can be found in the Pull Request.
Patch 2: gossipd Unknown SCIDs
Fortunately, Rusty Russell (@rustyrussell) had already been working on a PR introducing an automatic compaction (garbage collection) mechanism for the gossip map. The patch introduces a new function that queries the store for the number of live and dead records, triggering a compaction if there are at least four times as many dead records as live ones (num_dead < 4 * num_live). This PR inadvertently fixed the second memory exhaustion issue. More details can be found in the Pull Request.
Disclosure Timelines
Vulnerability 1 (connectd exhaustion)
-
22/07/2025: Fuzz target pushed upstream.
-
22/07/2025 - 31/07/2025: Improvements made to the target, dealing with false positives.
-
01/08/2025: Vulnerability confirmed using the attack program and disclosed to Matt (my SoB mentor at the time).
-
10/08/2025: Investigation performed. Cause of the memory usage pinpointed.
-
11/08/2025: Vulnerability independently confirmed by Matt.
-
12/08/2025: Final vulnerability report sent to CLN’s security mailing list.
-
14/08/2025: Reply by Rusty confirming the issue and disclosing the supposed fix.
-
18/08/2025: Fix merged to master.
Vulnerability 2 (gossipd exhaustion)
-
15/05/2026: Vulnerability confirmed using the original attack program.
-
17/05/2026: Investigation performed. Cause of the memory usage pinpointed to the SCID map.
-
18/05/2026: Final vulnerability report sent to CLN’s security mailing list.
-
21/05/2026: Reply by Rusty confirming the issue and identifying the compaction PR as the fix.
Lessons Learned
These bugs emphasize that internal interfaces are attack surfaces too. Even if connectd handles external network throttling, it must also defend itself against internal floods. Fuzzing the IPC layer (daemon-to-daemon) is just as critical as fuzzing the external network layer.
Furthermore, this serves as a reminder of the interconnected nature of software vulnerabilities. The second bug would have been found sooner if someone had simply run the attack program I provided during my initial disclosure. I had already publicly posted the disclosure for the connectd DoS on my personal blog before independently finding the gossipd bug using the exact same script. It was entirely possible that a malicious actor could have read my first post, reproduced the attack program, and successfully exploited the network using the (then-unknown) second attack path. It is a fortunate coincidence that the compaction code was being revised at the same time.
Special thanks to my SoB 2025 mentor, Matt Morehouse, for guiding the entire process, especially the triage and disclosure.