Boost _ Firedancer v0.1 34501 - [Blockchain_DLT - Medium] DoS in shreds validation

Submitted on Wed Aug 14 2024 01:10:30 GMT-0400 (Atlantic Standard Time) by @Swift77057 for Boost | Firedancer v0.1

Report ID: #34501

Report type: Blockchain/DLT

Report severity: Medium

Target: https://github.com/firedancer-io/firedancer/tree/e60d9a6206efaceac65a5a2c3a9e387a79d1d096

Impacts:

  • Liveness issues that cause Firedancer v0.1 validators to crash or be unavailable

Description

Summary

Due to differences in solana's and fd's shred validation, a malicious shred can pass fd's checks while causing a panic in the solana backend.

Description

Frankendancer's shred tile handles receiving and verifying shreds. When a full FEC set is constructed, the shred tile sends it to the store tile, which contains a simple wrapper around solana's blockstore.

pub extern "C" fn fd_ext_blockstore_insert_shreds(blockstore: *const std::ffi::c_void, shred_cnt: u64, shred_bytes: *const u8, shred_sz: u64, stride: u64, is_trusted: i32) {
    let blockstore = unsafe { &*(blockstore as *const Blockstore) };
    let shred_bytes = unsafe { std::slice::from_raw_parts(shred_bytes, (stride * (shred_cnt - 1) + shred_sz) as usize) };
    let shreds = (0..shred_cnt).map(|i| {
        let shred: &[u8] = &shred_bytes[(stride*i) as usize..(stride*i+shred_sz) as usize];
        Shred::new_from_serialized_shred(shred.to_vec()).unwrap()
    }).collect();

    /* The unwrap() here is not a mistake or laziness.  We do not
       expect inserting shreds to fail, and cannot recover if it does.
       Solana Labs panics if this happens and Firedancer will as well. */
    blockstore.insert_shreds(shreds, None, is_trusted!=0).unwrap();
}

This is the function on the solana side, that is called by the store tile to insert the validated shreds into the blockstore.

Note the comment about the unwrap() of the insertion function's return value. Any error here would cause the tile to crash due to panic, which should never happen.

This means that all the checks that the blockstore performs on the shreds, must also be performed by the shred tile. Otherwise, a shred might pass all of fd's checks, but later on cause an error (and thus, crash) in the blockstore code.

I found a shred (through fuzzing) that violates this constraint. It triggers an error in the blockstore code, because some flags in the shred are not set properly. The error is triggered in solana's data shred sanitization function:

So just setting the data header's flag field to LAST_SHRED_IN_SLOT without setting DATA_COMPLETE_SHRED as well, will produce a panic like this:

Especially note that the header's flags are never checked by fd at all.

In a real world scenario, to trigger this bug, one would need a real signature on the shred. This constraint was removed for the PoC, to ease fuzzing/testing.

Finally, please note the solana code itself is not vulnerable. The vulnerability manifests in how fd uses solana's code with this external function and the lack of proper validation of the flags field.

PoC

For the PoC, apply the .diff included in this report. The patch basically disables signature/merkle tree hash verification, as well as sending the shred to other network participants, for convenience. Note that these changes do not affect the actual shred validation, and a properly signed and constructed shred will trigger this bug in a real environment (also remember the absence of any checks on the flags value in fd). Use the script to send the shred to your fddev instance.

Proof of concept

Proof of Concept

patch.diff

send_shred.py

xxd shred_sol_crash.bin

Last updated

Was this helpful?