#42752 [BC-High] Signer can be DOSed through their libp2p component
Submitted on Mar 25th 2025 at 19:31:11 UTC by @leadwiz for Attackathon | Stacks II
Report ID: #42752
Report Type: Blockchain/DLT
Report severity: High
Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_1.0
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Brief/Intro
The Signer service in the protocol stack implements a libp2p-based peer-to-peer networking layer via SwarmBuilder. While this enables decentralized communication for signing duties (e.g., pubsub-based coordination), it also introduces a significant Denial of Service (DoS) vulnerability. Any external peer can initiate TCP/libp2p connections to the signer node and cause resource exhaustion — leading to degraded performance or full signer unavailability.
This issue arises from unprotected usage of the SwarmBuilder and lack of early-stage peer filtering or connection gating.
Vulnerability Details
The signer node initializes a libp2p swarm as follows:
async fn run_libp2p_swarm(ctx: impl Context) -> Result<(), Error> {
tracing::info!("initializing the p2p network");
// Build the swarm.
tracing::debug!("building the libp2p swarm");
let config = ctx.config();
let enable_quic = config.signer.p2p.is_quic_used();
let mut swarm = SignerSwarmBuilder::new(&config.signer.private_key)
.add_listen_endpoints(&ctx.config().signer.p2p.listen_on)
.add_seed_addrs(&ctx.config().signer.p2p.seeds)
.add_external_addresses(&ctx.config().signer.p2p.public_endpoints)
.enable_mdns(config.signer.p2p.enable_mdns)
.enable_quic_transport(enable_quic)
.build()?;
// Start the libp2p swarm. This will run until either the shutdown signal is
// received, or an unrecoverable error has occurred.
tracing::info!("starting the libp2p swarm");
swarm
.start(&ctx)
.in_current_span()
.await
.map_err(Error::SignerSwarm)
}This opens a public port (typically /ip4/0.0.0.0/tcp/PORT/p2p/PEER_ID) which any peer can connect to. There are no restrictions at the connection layer (e.g., libp2p's ConnectionGater) or rate limits. As a result, the signer accepts and processes incoming libp2p connection attempts from any peer, which includes:
TCP connection establishment
libp2p handshake (identify, ping, etc.)
potential gossip/ping/autonat protocols
Even if peers are later rejected based on business logic (is_allowed_peer()), this happens after connection establishment, consuming CPU and memory.
Impact Details
This vulnerability enables an unauthenticated attacker to:
Initiate hundreds or thousands of connections per second to the signer node.
Consume system resources (CPU, memory, file descriptors).
Stall or disrupt signing operations due to swarm loop congestion or libp2p thread starvation.
Prevent authorized peers from connecting or propagating signing messages in time.
Potentially crash the signer due to resource exhaustion, or trigger OOM kills if not rate-limited by OS.
The signer is responsible for critical operations and availability is essential.
This risk is amplified if:
The signer is part of a threshold set (and quorum depends on its presence).
There is no auto-recovery or failover mechanism.
Recommendations
To mitigate this issue:
Set
ConnectionLimitsonSwarmBuilderto cap incoming and total connections:Track and rate-limit connections by IP address using a lightweight in-memory structure.
References
Proof of Concept
Proof of Concept
Change
docker/docker-compose.ymlso the p2p port is exposed.
Run
make devenv-upReplace the in the script below and run the script, while monitoring the CPU consumption.
Was this helpful?