Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(tranquil-store): cargo-fuzz targets with asan+ubsan

Lewis: May this revision serve well! <lu5a@proton.me>

+345
+4
crates/tranquil-store/fuzz/.gitignore
··· 1 + target/ 2 + artifacts/ 3 + coverage/ 4 + Cargo.lock
+52
crates/tranquil-store/fuzz/Cargo.toml
··· 1 + [package] 2 + name = "tranquil-store-fuzz" 3 + version = "0.0.0" 4 + publish = false 5 + edition = "2024" 6 + 7 + [package.metadata] 8 + cargo-fuzz = true 9 + 10 + [dependencies] 11 + libfuzzer-sys = "0.4" 12 + arbitrary = { version = "1", features = ["derive"] } 13 + tranquil-store = { path = "..", features = ["test-harness"] } 14 + tempfile = "3" 15 + tokio = { version = "1", features = ["rt", "time", "macros", "sync"] } 16 + 17 + [[bin]] 18 + name = "decode_block_record" 19 + path = "fuzz_targets/decode_block_record.rs" 20 + test = false 21 + doc = false 22 + bench = false 23 + 24 + [[bin]] 25 + name = "decode_hint_record" 26 + path = "fuzz_targets/decode_hint_record.rs" 27 + test = false 28 + doc = false 29 + bench = false 30 + 31 + [[bin]] 32 + name = "segment_scan" 33 + path = "fuzz_targets/segment_scan.rs" 34 + test = false 35 + doc = false 36 + bench = false 37 + 38 + [[bin]] 39 + name = "metastore_key_codec" 40 + path = "fuzz_targets/metastore_key_codec.rs" 41 + test = false 42 + doc = false 43 + bench = false 44 + 45 + [[bin]] 46 + name = "gauntlet_micro" 47 + path = "fuzz_targets/gauntlet_micro.rs" 48 + test = false 49 + doc = false 50 + bench = false 51 + 52 + [workspace]
+1
crates/tranquil-store/fuzz/corpus/decode_block_record/cid_only
··· 1 + 
crates/tranquil-store/fuzz/corpus/decode_block_record/zero_cid_truncated

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/decode_hint_record/truncated

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/decode_hint_record/two_zero_records

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/decode_hint_record/zero_record

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/gauntlet_micro/one_op

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/gauntlet_micro/two_ops

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/metastore_key_codec/one_u64_roundtrip

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/metastore_key_codec/raw_short

This is a binary file and will not be displayed.

crates/tranquil-store/fuzz/corpus/metastore_key_codec/raw_zeros_32

This is a binary file and will not be displayed.

+1
crates/tranquil-store/fuzz/corpus/segment_scan/bad_magic
··· 1 + BADX
crates/tranquil-store/fuzz/corpus/segment_scan/header_and_zeros

This is a binary file and will not be displayed.

+1
crates/tranquil-store/fuzz/corpus/segment_scan/header_only
··· 1 + TQEV
+28
crates/tranquil-store/fuzz/fuzz_targets/decode_block_record.rs
··· 1 + #![no_main] 2 + 3 + use std::path::Path; 4 + 5 + use libfuzzer_sys::fuzz_target; 6 + use tranquil_store::blockstore::{BlockOffset, decode_block_record}; 7 + use tranquil_store::{FaultConfig, OpenOptions, SimulatedIO, StorageIO}; 8 + 9 + fuzz_target!(|data: &[u8]| { 10 + let sim = SimulatedIO::new(0, FaultConfig::none()); 11 + let opts = OpenOptions { 12 + read: true, 13 + write: true, 14 + create: true, 15 + truncate: false, 16 + }; 17 + let fd = match sim.open(Path::new("/fuzz/block.tqb"), opts) { 18 + Ok(fd) => fd, 19 + Err(_) => return, 20 + }; 21 + if !data.is_empty() { 22 + let _ = sim.write_all_at(fd, 0, data); 23 + let _ = sim.sync(fd); 24 + } 25 + let file_size = data.len() as u64; 26 + let _ = decode_block_record(&sim, fd, BlockOffset::new(0), file_size); 27 + let _ = sim.close(fd); 28 + });
+41
crates/tranquil-store/fuzz/fuzz_targets/decode_hint_record.rs
··· 1 + #![no_main] 2 + 3 + use std::path::Path; 4 + 5 + use libfuzzer_sys::fuzz_target; 6 + use tranquil_store::blockstore::{HintOffset, decode_hint_record}; 7 + use tranquil_store::{FaultConfig, OpenOptions, SimulatedIO, StorageIO}; 8 + 9 + fuzz_target!(|data: &[u8]| { 10 + let sim = SimulatedIO::new(0, FaultConfig::none()); 11 + let opts = OpenOptions { 12 + read: true, 13 + write: true, 14 + create: true, 15 + truncate: false, 16 + }; 17 + let fd = match sim.open(Path::new("/fuzz/hint.tqh"), opts) { 18 + Ok(fd) => fd, 19 + Err(_) => return, 20 + }; 21 + if !data.is_empty() { 22 + let _ = sim.write_all_at(fd, 0, data); 23 + let _ = sim.sync(fd); 24 + } 25 + let file_size = data.len() as u64; 26 + let cursor = std::cell::Cell::new(0u64); 27 + std::iter::from_fn(|| { 28 + if cursor.get() >= file_size { 29 + return None; 30 + } 31 + match decode_hint_record(&sim, fd, HintOffset::new(cursor.get()), file_size) { 32 + Ok(Some(_)) => { 33 + cursor.set(cursor.get() + 64); 34 + Some(()) 35 + } 36 + _ => None, 37 + } 38 + }) 39 + .for_each(|()| {}); 40 + let _ = sim.close(fd); 41 + });
+109
crates/tranquil-store/fuzz/fuzz_targets/gauntlet_micro.rs
··· 1 + #![no_main] 2 + 3 + use std::sync::OnceLock; 4 + 5 + use arbitrary::{Arbitrary, Unstructured}; 6 + use libfuzzer_sys::fuzz_target; 7 + use tokio::runtime::Runtime; 8 + use tranquil_store::blockstore::GroupCommitConfig; 9 + use tranquil_store::gauntlet::{ 10 + CollectionName, DidSpaceSize, Gauntlet, GauntletConfig, InvariantSet, IoBackend, KeySpaceSize, 11 + MaxFileSize, Op, OpCount, OpInterval, OpStream, OpWeights, RecordKey, RestartPolicy, 12 + RetentionMaxSecs, RunLimits, Seed, ShardCount, SizeDistribution, StoreConfig, ValueBytes, 13 + ValueSeed, WallMs, WorkloadModel, WriterConcurrency, 14 + }; 15 + 16 + #[derive(Arbitrary, Debug)] 17 + enum FuzzOp { 18 + Add { rkey: u8, value: u16 }, 19 + Delete { rkey: u8 }, 20 + Compact, 21 + Checkpoint, 22 + Read { rkey: u8 }, 23 + ReadBlock { value: u16 }, 24 + } 25 + 26 + const COLLECTION: &str = "app.bsky.feed.post"; 27 + const MAX_OPS: usize = 128; 28 + 29 + fn to_op(fuzz_op: FuzzOp) -> Op { 30 + match fuzz_op { 31 + FuzzOp::Add { rkey, value } => Op::AddRecord { 32 + collection: CollectionName(COLLECTION.to_string()), 33 + rkey: RecordKey(format!("k{rkey:03}")), 34 + value_seed: ValueSeed(u32::from(value)), 35 + }, 36 + FuzzOp::Delete { rkey } => Op::DeleteRecord { 37 + collection: CollectionName(COLLECTION.to_string()), 38 + rkey: RecordKey(format!("k{rkey:03}")), 39 + }, 40 + FuzzOp::Compact => Op::Compact, 41 + FuzzOp::Checkpoint => Op::Checkpoint, 42 + FuzzOp::Read { rkey } => Op::ReadRecord { 43 + collection: CollectionName(COLLECTION.to_string()), 44 + rkey: RecordKey(format!("k{rkey:03}")), 45 + }, 46 + FuzzOp::ReadBlock { value } => Op::ReadBlock { 47 + value_seed: ValueSeed(u32::from(value)), 48 + }, 49 + } 50 + } 51 + 52 + fn tiny_config() -> GauntletConfig { 53 + GauntletConfig { 54 + seed: Seed(0), 55 + io: IoBackend::Real, 56 + workload: WorkloadModel { 57 + weights: OpWeights::default(), 58 + size_distribution: SizeDistribution::Fixed(ValueBytes(64)), 59 + collections: vec![CollectionName(COLLECTION.to_string())], 60 + key_space: KeySpaceSize(256), 61 + did_space: DidSpaceSize(8), 62 + retention_max_secs: RetentionMaxSecs(3600), 63 + }, 64 + op_count: OpCount(0), 65 + invariants: InvariantSet::REFCOUNT_CONSERVATION 66 + | InvariantSet::REACHABILITY 67 + | InvariantSet::READ_AFTER_WRITE, 68 + limits: RunLimits { 69 + max_wall_ms: Some(WallMs(2_000)), 70 + }, 71 + restart_policy: RestartPolicy::EveryNOps(OpInterval(32)), 72 + store: StoreConfig { 73 + max_file_size: MaxFileSize(4096), 74 + group_commit: GroupCommitConfig::default(), 75 + shard_count: ShardCount(1), 76 + }, 77 + eventlog: None, 78 + writer_concurrency: WriterConcurrency(1), 79 + } 80 + } 81 + 82 + fn shared_runtime() -> &'static Runtime { 83 + static RUNTIME: OnceLock<Runtime> = OnceLock::new(); 84 + RUNTIME.get_or_init(|| { 85 + tokio::runtime::Builder::new_current_thread() 86 + .enable_time() 87 + .build() 88 + .expect("build tokio runtime") 89 + }) 90 + } 91 + 92 + fuzz_target!(|data: &[u8]| { 93 + if data.is_empty() { 94 + return; 95 + } 96 + let mut u = Unstructured::new(data); 97 + let ops: Vec<FuzzOp> = match Vec::<FuzzOp>::arbitrary(&mut u) { 98 + Ok(ops) => ops.into_iter().take(MAX_OPS).collect(), 99 + Err(_) => return, 100 + }; 101 + if ops.is_empty() { 102 + return; 103 + } 104 + let stream = OpStream::from_vec(ops.into_iter().map(to_op).collect()); 105 + 106 + let cfg = tiny_config(); 107 + let gauntlet = Gauntlet::new(cfg).expect("build gauntlet"); 108 + let _ = shared_runtime().block_on(gauntlet.run_with_ops(stream)); 109 + });
+77
crates/tranquil-store/fuzz/fuzz_targets/metastore_key_codec.rs
··· 1 + #![no_main] 2 + 3 + use arbitrary::Arbitrary; 4 + use libfuzzer_sys::fuzz_target; 5 + use tranquil_store::metastore::encoding::{KeyBuilder, KeyReader}; 6 + 7 + #[derive(Arbitrary, Debug, PartialEq, Eq)] 8 + enum Field { 9 + U64(u64), 10 + I64(i64), 11 + U32(u32), 12 + U16(u16), 13 + Bool(bool), 14 + Bytes(Vec<u8>), 15 + String(String), 16 + } 17 + 18 + fn append(builder: KeyBuilder, field: &Field) -> KeyBuilder { 19 + match field { 20 + Field::U64(v) => builder.u64(*v), 21 + Field::I64(v) => builder.i64(*v), 22 + Field::U32(v) => builder.u32(*v), 23 + Field::U16(v) => builder.u16(*v), 24 + Field::Bool(v) => builder.bool(*v), 25 + Field::Bytes(v) => builder.bytes(v), 26 + Field::String(v) => builder.string(v), 27 + } 28 + } 29 + 30 + fn consume(reader: &mut KeyReader<'_>, field: &Field) -> bool { 31 + match field { 32 + Field::U64(v) => reader.u64() == Some(*v), 33 + Field::I64(v) => reader.i64() == Some(*v), 34 + Field::U32(v) => reader.u32() == Some(*v), 35 + Field::U16(v) => reader.u16() == Some(*v), 36 + Field::Bool(v) => reader.bool() == Some(*v), 37 + Field::Bytes(v) => reader.bytes().as_deref() == Some(v.as_slice()), 38 + Field::String(v) => reader.string().as_deref() == Some(v.as_str()), 39 + } 40 + } 41 + 42 + #[derive(Arbitrary, Debug)] 43 + enum Mode { 44 + Roundtrip(Vec<Field>), 45 + Raw(Vec<u8>), 46 + } 47 + 48 + fuzz_target!(|mode: Mode| { 49 + match mode { 50 + Mode::Roundtrip(fields) => { 51 + let encoded1 = fields.iter().fold(KeyBuilder::new(), append).build(); 52 + 53 + let mut reader = KeyReader::new(encoded1.as_slice()); 54 + let all_match = fields.iter().all(|f| consume(&mut reader, f)); 55 + assert!(all_match, "roundtrip decode failed"); 56 + assert!(reader.is_empty(), "trailing bytes after decode"); 57 + 58 + let encoded2 = fields.iter().fold(KeyBuilder::new(), append).build(); 59 + assert_eq!( 60 + encoded1.as_slice(), 61 + encoded2.as_slice(), 62 + "encoding not deterministic", 63 + ); 64 + } 65 + Mode::Raw(data) => { 66 + let mut reader = KeyReader::new(&data); 67 + let _ = reader.u64(); 68 + let _ = reader.i64(); 69 + let _ = reader.u32(); 70 + let _ = reader.u16(); 71 + let _ = reader.bool(); 72 + let _ = reader.bytes(); 73 + let _ = reader.string(); 74 + let _ = reader.tag(); 75 + } 76 + } 77 + });
+31
crates/tranquil-store/fuzz/fuzz_targets/segment_scan.rs
··· 1 + #![no_main] 2 + 3 + use std::path::Path; 4 + 5 + use libfuzzer_sys::fuzz_target; 6 + use tranquil_store::eventlog::SegmentReader; 7 + 8 + const FUZZ_MAX_PAYLOAD: u32 = 1 << 20; 9 + use tranquil_store::{FaultConfig, OpenOptions, SimulatedIO, StorageIO}; 10 + 11 + fuzz_target!(|data: &[u8]| { 12 + let sim = SimulatedIO::new(0, FaultConfig::none()); 13 + let opts = OpenOptions { 14 + read: true, 15 + write: true, 16 + create: true, 17 + truncate: false, 18 + }; 19 + let fd = match sim.open(Path::new("/fuzz/segment.tqe"), opts) { 20 + Ok(fd) => fd, 21 + Err(_) => return, 22 + }; 23 + if !data.is_empty() { 24 + let _ = sim.write_all_at(fd, 0, data); 25 + let _ = sim.sync(fd); 26 + } 27 + if let Ok(reader) = SegmentReader::open(&sim, fd, FUZZ_MAX_PAYLOAD) { 28 + reader.for_each(|_result| {}); 29 + } 30 + let _ = sim.close(fd); 31 + });