Skip to content

panicking at storage/src/ordinal/storage.rs:281 #2069

@dnkolegov-ar

Description

@dnkolegov-ar

thread panicked at storage/src/ordinal/storage.rs:281
called Option::unwrap() on a None value

PoC:

#![no_main]

use arbitrary::{Arbitrary, Unstructured};
use commonware_runtime::{deterministic, Metrics, Runner, Storage};
use commonware_storage::ordinal::{Config, Ordinal};
use commonware_utils::sequence::FixedBytes;
use core::num::{NonZeroU64, NonZeroUsize};
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Clone)]
struct Entry {
    index: u64,
    value: [u8; 32],
}

#[derive(Arbitrary, Clone)]
struct FuzzInput {
    partition_seed: u64,
    items_per_blob_hint: u16,
    write_buffer_hint: u16,
    replay_buffer_hint: u16,
    prune_offset: u64,
    selection_hint: u16,
    entries: Vec<Entry>,
}

async fn run_case(context: deterministic::Context, input: FuzzInput) {
    let FuzzInput {
        partition_seed,
        items_per_blob_hint,
        write_buffer_hint,
        replay_buffer_hint,
        prune_offset,
        selection_hint,
        entries,
    } = input;

    let items_per_blob_raw = u64::from(items_per_blob_hint % 64).saturating_add(1);
    let Some(items_per_blob) = NonZeroU64::new(items_per_blob_raw) else {
        return;
    };

    let write_buffer_raw = usize::from(write_buffer_hint % 4095).saturating_add(1);
    let Some(write_buffer) = NonZeroUsize::new(write_buffer_raw) else {
        return;
    };

    let replay_buffer_raw = usize::from(replay_buffer_hint % 4095).saturating_add(1);
    let Some(replay_buffer) = NonZeroUsize::new(replay_buffer_raw) else {
        return;
    };

    let partition = format!("generated_25_prune_fuzz_{partition_seed}");
    let config = Config {
        partition: partition.clone(),
        items_per_blob,
        write_buffer,
        replay_buffer,
    };

    let ordinal_context: deterministic::Context = context.with_label("ordinal");
    let mut store: Ordinal<deterministic::Context, FixedBytes<32>> =
        match Ordinal::init(ordinal_context, config).await {
            Ok(store) => store,
            Err(_) => return,
        };

    let mut indices = Vec::new();
    for entry in entries.into_iter().take(32) {
        let index = entry.index % 4096;
        if store
            .put(index, FixedBytes::new(entry.value))
            .await
            .is_err()
        {
            return;
        }
        indices.push(index);
    }

    if indices.is_empty() {
        return;
    }

    if store.sync().await.is_err() {
        return;
    }

    let choice = usize::from(selection_hint) % indices.len();
    let target_index = indices[choice];
    let items_per_blob_value = items_per_blob.get();
    let target_section = target_index / items_per_blob_value;

    let section_name = target_section.to_be_bytes();
    if context
        .remove(&partition, Some(&section_name))
        .await
        .is_err()
    {
        return;
    }

    let prune_min_base = target_section
        .saturating_add(1)
        .saturating_mul(items_per_blob_value);
    let prune_min = prune_min_base.saturating_add(prune_offset % items_per_blob_value);

    let _ = store.prune(prune_min).await;
    let _ = store.get(target_index).await;
}

fuzz_target!(|data: &[u8]| {
    let mut unstructured = Unstructured::new(data);
    let Ok(input) = FuzzInput::arbitrary(&mut unstructured) else {
        return;
    };

    let runner = deterministic::Runner::default();
    runner.start(move |context| {
        let params = input.clone();
        async move {
            run_case(context, params).await;
        }
    });
});

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions