From ade4d07bb5351f56be38547ed7e13848bba3b081 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Tue, 28 Apr 2026 15:46:17 +0200 Subject: [PATCH 1/9] Set up fuzz infrastructure --- Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ fuzz/.gitignore | 4 ++++ fuzz/Cargo.toml | 20 ++++++++++++++++++++ src/model/Cargo.toml | 2 ++ src/util/Cargo.toml | 2 ++ src/util/crdt/lww.rs | 5 +++++ src/util/data.rs | 1 + 8 files changed, 78 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index a8621e8d..2e5d4211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arc-swap" version = "1.9.1" @@ -1116,6 +1125,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1523,6 +1543,16 @@ dependencies = [ "utoipa", ] +[[package]] +name = "garage-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "garage_model", + "garage_table", + "libfuzzer-sys", +] + [[package]] name = "garage_api_admin" version = "2.3.0" @@ -1702,6 +1732,7 @@ dependencies = [ name = "garage_model" version = "2.3.0" dependencies = [ + "arbitrary", "argon2", "async-trait", "base64 0.22.1", @@ -1810,6 +1841,7 @@ dependencies = [ name = "garage_util" version = "2.3.0" dependencies = [ + "arbitrary", "arc-swap", "async-trait", "blake2", @@ -2934,6 +2966,16 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libsodium-sys" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index 9cd06778..d6876b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "src/garage", "src/k2v-client", "src/format-table", + "fuzz", ] default-members = ["src/garage"] @@ -40,6 +41,7 @@ k2v-client = { version = "0.0.4", path = "src/k2v-client" } # External crates from crates.io arc-swap = "1.8" +arbitrary = { version = "1.4.2", features = ["derive"] } argon2 = "0.5" async-trait = "0.1" backtrace = "0.3" diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..1a45eee7 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..b5cb21e5 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "garage-fuzz" +version = "0.0.0" +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { version = "1.4.2", features = ["derive"] } +libfuzzer-sys = "0.4" + +[dependencies.garage_table] +path = "../src/table" + +[dependencies.garage_model] +path = "../src/model" +features = ["arbitrary"] + diff --git a/src/model/Cargo.toml b/src/model/Cargo.toml index 12683a0e..bb3d9aac 100644 --- a/src/model/Cargo.toml +++ b/src/model/Cargo.toml @@ -21,6 +21,7 @@ garage_block.workspace = true garage_util.workspace = true garage_net.workspace = true +arbitrary = { optional = true, workspace = true } argon2.workspace = true async-trait.workspace = true blake2.workspace = true @@ -46,6 +47,7 @@ k2v = ["garage_util/k2v"] lmdb = ["garage_db/lmdb"] sqlite = ["garage_db/sqlite"] fjall = ["garage_db/fjall"] +arbitrary = ["dep:arbitrary","garage_util/arbitrary"] [lints] workspace = true diff --git a/src/util/Cargo.toml b/src/util/Cargo.toml index cbba213c..37799fd7 100644 --- a/src/util/Cargo.toml +++ b/src/util/Cargo.toml @@ -17,6 +17,7 @@ path = "lib.rs" garage_db.workspace = true garage_net.workspace = true +arbitrary = { optional = true, workspace = true } arc-swap.workspace = true async-trait.workspace = true blake2.workspace = true @@ -52,6 +53,7 @@ mktemp.workspace = true [features] k2v = [] +arbitrary = ["dep:arbitrary"] [lints] workspace = true diff --git a/src/util/crdt/lww.rs b/src/util/crdt/lww.rs index f8b03b85..e18ab5f3 100644 --- a/src/util/crdt/lww.rs +++ b/src/util/crdt/lww.rs @@ -84,6 +84,11 @@ where self.v = new_value; } + pub fn update2(&mut self, f: impl FnOnce(&mut T), time: u64) { + self.ts = std::cmp::max(self.ts + 1, time); + f(&mut self.v) + } + /// Get the timestamp currently associated with the value pub fn timestamp(&self) -> u64 { self.ts diff --git a/src/util/data.rs b/src/util/data.rs index e4ce316b..63c4379f 100644 --- a/src/util/data.rs +++ b/src/util/data.rs @@ -6,6 +6,7 @@ use std::fmt; /// An array of 32 bytes #[derive(Default, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct FixedBytes32([u8; 32]); impl From<[u8; 32]> for FixedBytes32 { From 9a1825941996f3045d0f065302efa8a5c34e50ca Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Mon, 27 Apr 2026 15:48:11 +0200 Subject: [PATCH 2/9] Add rust toolchain toml in fuzz dir --- fuzz/rust-toolchain.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fuzz/rust-toolchain.toml diff --git a/fuzz/rust-toolchain.toml b/fuzz/rust-toolchain.toml new file mode 100644 index 00000000..5d56faf9 --- /dev/null +++ b/fuzz/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" From 6ddae5397c0a923fa55c57c8ef730c6384c146f8 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Mon, 27 Apr 2026 16:59:13 +0200 Subject: [PATCH 3/9] Add version table fuzz --- fuzz/Cargo.toml | 6 ++ fuzz/fuzz_targets/version_crdt.rs | 94 +++++++++++++++++++++++++++++++ src/model/s3/version_table.rs | 3 + 3 files changed, 103 insertions(+) create mode 100644 fuzz/fuzz_targets/version_crdt.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b5cb21e5..124fc28f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -18,3 +18,9 @@ path = "../src/table" path = "../src/model" features = ["arbitrary"] +[[bin]] +name = "version_crdt" +path = "fuzz_targets/version_crdt.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/version_crdt.rs b/fuzz/fuzz_targets/version_crdt.rs new file mode 100644 index 00000000..4be47666 --- /dev/null +++ b/fuzz/fuzz_targets/version_crdt.rs @@ -0,0 +1,94 @@ +#![no_main] + +use garage_model::s3::version_table::{Version, VersionBacklink, VersionBlock, VersionBlockKey}; +use garage_table::crdt::Crdt; +use libfuzzer_sys::fuzz_target; + +/// Build a Version from an arbitrary deleted flag and block list, using a fixed uuid/backlink +/// so that CRDT state can be compared across merge results. +/// Duplicate block keys are dropped before construction. +/// If deleted, blocks are cleared to ensure a valid initial CRDT state. +fn make_version(deleted: bool, mut blocks: Vec<(VersionBlockKey, VersionBlock)>) -> Version { + blocks.sort_by_key(|(k, _)| *k); + blocks.dedup_by_key(|(k, _)| *k); + let mut v = Version::new( + [0u8; 32].into(), + VersionBacklink::Object { + bucket_id: [0u8; 32].into(), + key: String::new(), + }, + deleted, + ); + for (key, block) in blocks { + v.blocks.put(key, block); + } + if v.deleted.get() { + v.blocks.clear(); + } + v +} + +fn crdt_state(v: &Version) -> (bool, &[(VersionBlockKey, VersionBlock)]) { + (v.deleted.get(), v.blocks.items()) +} + +fuzz_target!(|inputs: ( + (bool, Vec<(VersionBlockKey, VersionBlock)>), + (bool, Vec<(VersionBlockKey, VersionBlock)>), + (bool, Vec<(VersionBlockKey, VersionBlock)>) +)| { + let ((d1, b1), (d2, b2), (d3, b3)) = inputs; + let a = make_version(d1, b1); + let b = make_version(d2, b2); + let c = make_version(d3, b3); + + // Idempotency: merge(a, a) == a + { + let mut a2 = a.clone(); + a2.merge(&a.clone()); + assert_eq!( + crdt_state(&a2), + crdt_state(&a), + "merge is not idempotent: {a2:#?} != {a:#?}" + ); + } + + // Commutativity: crdt_state(merge(a, b)) == crdt_state(merge(b, a)) + let ab = { + let mut t = a.clone(); + t.merge(&b); + t + }; + let ba = { + let mut t = b.clone(); + t.merge(&a); + t + }; + assert_eq!( + crdt_state(&ab), + crdt_state(&ba), + "merge is not commutative: {ab:#?} != {ba:#?}" + ); + + // Associativity: crdt_state(merge(merge(a, b), c)) == crdt_state(merge(a, merge(b, c))) + let ab_c = { + let mut t = ab.clone(); + t.merge(&c); + t + }; + let bc = { + let mut t = b.clone(); + t.merge(&c); + t + }; + let a_bc = { + let mut t = a.clone(); + t.merge(&bc); + t + }; + assert_eq!( + crdt_state(&ab_c), + crdt_state(&a_bc), + "merge is not associative: {ab_c:#?} != {a_bc:#?}" + ); +}); diff --git a/src/model/s3/version_table.rs b/src/model/s3/version_table.rs index 45be5af8..13a37166 100644 --- a/src/model/s3/version_table.rs +++ b/src/model/s3/version_table.rs @@ -41,6 +41,7 @@ mod v08 { } #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct VersionBlockKey { /// Number of the part pub part_number: u64, @@ -51,6 +52,7 @@ mod v08 { /// Information about a single block #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct VersionBlock { /// Blake2 sum of the block pub hash: Hash, @@ -91,6 +93,7 @@ pub(crate) mod v09 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum VersionBacklink { Object { /// Bucket in which the related object is stored From 6a097e7de339a6578034ec2a5ac8e6aede7cbc67 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Mon, 27 Apr 2026 17:31:49 +0200 Subject: [PATCH 4/9] Add MPU table --- fuzz/Cargo.toml | 7 +++ fuzz/fuzz_targets/mpu_crdt.rs | 96 +++++++++++++++++++++++++++++++++++ src/model/s3/mpu_table.rs | 2 + src/model/s3/object_table.rs | 1 + 4 files changed, 106 insertions(+) create mode 100644 fuzz/fuzz_targets/mpu_crdt.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 124fc28f..899416e5 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -24,3 +24,10 @@ path = "fuzz_targets/version_crdt.rs" test = false doc = false bench = false + +[[bin]] +name = "mpu_crdt" +path = "fuzz_targets/mpu_crdt.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/mpu_crdt.rs b/fuzz/fuzz_targets/mpu_crdt.rs new file mode 100644 index 00000000..b3e5130e --- /dev/null +++ b/fuzz/fuzz_targets/mpu_crdt.rs @@ -0,0 +1,96 @@ +#![no_main] + +use garage_model::s3::mpu_table::{MpuPart, MpuPartKey, MultipartUpload}; +use garage_table::crdt::Crdt; +use libfuzzer_sys::fuzz_target; + +/// Build a MultipartUpload from an arbitrary deleted flag and parts list, using a fixed +/// upload_id/bucket_id/key so that CRDT state can be compared across merge results. +/// Duplicate part keys are dropped before construction. +/// `MpuPart.version` is fixed to a constant since it is identity data, not CRDT state: +/// two replicas of the same part (same MpuPartKey) always share the same version UUID. +/// If deleted, parts are cleared to ensure a valid initial CRDT state. +fn make_mpu(deleted: bool, mut parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload { + parts.sort_by_key(|(k, _)| *k); + parts.dedup_by_key(|(k, _)| *k); + let mut mpu = MultipartUpload::new( + [0u8; 32].into(), + 0, + [0u8; 32].into(), + String::new(), + deleted, + ); + for (key, mut part) in parts { + part.version = [0u8; 32].into(); + mpu.parts.put(key, part); + } + if mpu.deleted.get() { + mpu.parts.clear(); + } + mpu +} + +fn crdt_state(mpu: &MultipartUpload) -> (bool, &[(MpuPartKey, MpuPart)]) { + (mpu.deleted.get(), mpu.parts.items()) +} + +fuzz_target!(|inputs: ( + (bool, Vec<(MpuPartKey, MpuPart)>), + (bool, Vec<(MpuPartKey, MpuPart)>), + (bool, Vec<(MpuPartKey, MpuPart)>) +)| { + let ((d1, p1), (d2, p2), (d3, p3)) = inputs; + let a = make_mpu(d1, p1); + let b = make_mpu(d2, p2); + let c = make_mpu(d3, p3); + + // Idempotency: merge(a, a) == a + { + let mut a2 = a.clone(); + a2.merge(&a.clone()); + assert_eq!( + crdt_state(&a2), + crdt_state(&a), + "merge is not idempotent: {a2:#?} != {a:#?}" + ); + } + + // Commutativity: crdt_state(merge(a, b)) == crdt_state(merge(b, a)) + let ab = { + let mut t = a.clone(); + t.merge(&b); + t + }; + let ba = { + let mut t = b.clone(); + t.merge(&a); + t + }; + assert_eq!( + crdt_state(&ab), + crdt_state(&ba), + "merge is not commutative: {ab:#?} != {ba:#?}" + ); + + // Associativity: crdt_state(merge(merge(a, b), c)) == crdt_state(merge(a, merge(b, c))) + let ab_c = { + let mut t = ab.clone(); + t.merge(&c); + t + }; + let bc = { + let mut t = b.clone(); + t.merge(&c); + t + }; + let a_bc = { + let mut t = a.clone(); + t.merge(&bc); + t + }; + assert_eq!( + crdt_state(&ab_c), + crdt_state(&a_bc), + "merge is not associative: {ab_c:#?} != {a_bc:#?}" + ); +}); diff --git a/src/model/s3/mpu_table.rs b/src/model/s3/mpu_table.rs index 52734d27..dcae6344 100644 --- a/src/model/s3/mpu_table.rs +++ b/src/model/s3/mpu_table.rs @@ -48,6 +48,7 @@ mod v09 { } #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct MpuPartKey { /// Number of the part pub part_number: u64, @@ -57,6 +58,7 @@ mod v09 { /// The version of an uploaded part #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct MpuPart { /// Links to a Version in `VersionTable` pub version: Uuid, diff --git a/src/model/s3/object_table.rs b/src/model/s3/object_table.rs index aff4bbfd..b5bb7584 100644 --- a/src/model/s3/object_table.rs +++ b/src/model/s3/object_table.rs @@ -291,6 +291,7 @@ mod v010 { /// Checksum value for x-amz-checksum-algorithm #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum ChecksumValue { Crc32(#[serde(with = "serde_bytes")] [u8; 4]), Crc32c(#[serde(with = "serde_bytes")] [u8; 4]), From 322da7242b1e565e625eb1fb67284646388b2735 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Tue, 28 Apr 2026 17:56:03 +0200 Subject: [PATCH 5/9] Post review fixes --- Cargo.lock | 2 +- Cargo.toml | 3 ++- fuzz/Cargo.toml | 4 ++-- fuzz/fuzz_targets/mpu_crdt.rs | 5 +---- src/model/s3/version_table.rs | 1 - src/util/crdt/lww.rs | 5 ----- 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e5d4211..0294a62c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d6876b15..3ff6d5a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ k2v-client = { version = "0.0.4", path = "src/k2v-client" } # External crates from crates.io arc-swap = "1.8" -arbitrary = { version = "1.4.2", features = ["derive"] } +arbitrary = { version = "1.4.2"} argon2 = "0.5" async-trait = "0.1" backtrace = "0.3" @@ -61,6 +61,7 @@ hmac = "0.12" itertools = "0.14" ipnet = "2.11" lazy_static = "1.5" +libfuzzer-sys = "0.4" md-5 = "0.10" mktemp = "0.5" nix = { version = "0.31", default-features = false, features = ["fs"] } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 899416e5..92c81409 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" cargo-fuzz = true [dependencies] -arbitrary = { version = "1.4.2", features = ["derive"] } -libfuzzer-sys = "0.4" +arbitrary = { workspace = true, features = ["derive"]} +libfuzzer-sys = { workspace = true } [dependencies.garage_table] path = "../src/table" diff --git a/fuzz/fuzz_targets/mpu_crdt.rs b/fuzz/fuzz_targets/mpu_crdt.rs index b3e5130e..82d6b613 100644 --- a/fuzz/fuzz_targets/mpu_crdt.rs +++ b/fuzz/fuzz_targets/mpu_crdt.rs @@ -6,13 +6,10 @@ use libfuzzer_sys::fuzz_target; /// Build a MultipartUpload from an arbitrary deleted flag and parts list, using a fixed /// upload_id/bucket_id/key so that CRDT state can be compared across merge results. -/// Duplicate part keys are dropped before construction. /// `MpuPart.version` is fixed to a constant since it is identity data, not CRDT state: /// two replicas of the same part (same MpuPartKey) always share the same version UUID. /// If deleted, parts are cleared to ensure a valid initial CRDT state. -fn make_mpu(deleted: bool, mut parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload { - parts.sort_by_key(|(k, _)| *k); - parts.dedup_by_key(|(k, _)| *k); +fn make_mpu(deleted: bool, parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload { let mut mpu = MultipartUpload::new( [0u8; 32].into(), 0, diff --git a/src/model/s3/version_table.rs b/src/model/s3/version_table.rs index 13a37166..5124f556 100644 --- a/src/model/s3/version_table.rs +++ b/src/model/s3/version_table.rs @@ -93,7 +93,6 @@ pub(crate) mod v09 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] - #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum VersionBacklink { Object { /// Bucket in which the related object is stored diff --git a/src/util/crdt/lww.rs b/src/util/crdt/lww.rs index e18ab5f3..f8b03b85 100644 --- a/src/util/crdt/lww.rs +++ b/src/util/crdt/lww.rs @@ -84,11 +84,6 @@ where self.v = new_value; } - pub fn update2(&mut self, f: impl FnOnce(&mut T), time: u64) { - self.ts = std::cmp::max(self.ts + 1, time); - f(&mut self.v) - } - /// Get the timestamp currently associated with the value pub fn timestamp(&self) -> u64 { self.ts From a5650ea303143a293c8d2792b80fb9902be9352d Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Wed, 29 Apr 2026 14:24:33 +0200 Subject: [PATCH 6/9] Ignore typos in fuzz/ --- typos.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typos.toml b/typos.toml index f76d002d..70d63060 100644 --- a/typos.toml +++ b/typos.toml @@ -3,4 +3,4 @@ PN = "PN" substituters = "substituters" [files] -extend-exclude = ["CHANGELOG.md", "**.js", "**.svg", "doc/talks/*"] +extend-exclude = ["CHANGELOG.md", "**.js", "**.svg", "doc/talks/*", "fuzz/*"] From 7d97b2b96e839a6f75cff7ddb4fa0f23f40ce92c Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Wed, 29 Apr 2026 14:24:59 +0200 Subject: [PATCH 7/9] Ignore flaky test_items_and_indices From a25ad494ccd3a5640b5d4f73fd74774c9c471a10 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Thu, 30 Apr 2026 13:27:01 +0200 Subject: [PATCH 8/9] Add fuzzing README --- fuzz/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 fuzz/README.md diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..c098e5a8 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,11 @@ +# Fuzzing + +## Setup + +Install cargo fuzz: `cargo install cargo-fuzz` + +## Launch + +Run `cargo fuzz run ` where `` is the name (without extension) of one of the `.rs` files in the `fuzz_targets` directory. + +If you launch the command outside of the fuzz directory, you need to force the nightly toolchain with `cargo +nightly`. \ No newline at end of file From ddc42c89fbd9ee1eb4ff31b8428c1f15a1f23ac0 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 1 May 2026 17:30:12 +0200 Subject: [PATCH 9/9] add #fuzz devshell and make fuzzing work on nixos --- Cargo.lock | 1 + flake.nix | 8 ++++++++ fuzz/Cargo.toml | 9 +++------ nix/compile.nix | 8 ++++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0294a62c..06e65724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1548,6 +1548,7 @@ name = "garage-fuzz" version = "0.0.0" dependencies = [ "arbitrary", + "garage_db", "garage_model", "garage_table", "libfuzzer-sys", diff --git a/flake.nix b/flake.nix index 00f0efe1..25a3fee5 100644 --- a/flake.nix +++ b/flake.nix @@ -95,6 +95,14 @@ killall ]; }; + + # dev shell for fuzzing + fuzz = pkgs.mkShell { + buildInputs = with pkgs; [ + targets.toolchainNightly + cargo-fuzz + ]; + }; }; }); } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 92c81409..40b0a118 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,12 +11,9 @@ cargo-fuzz = true arbitrary = { workspace = true, features = ["derive"]} libfuzzer-sys = { workspace = true } -[dependencies.garage_table] -path = "../src/table" - -[dependencies.garage_model] -path = "../src/model" -features = ["arbitrary"] +garage_db.workspace = true +garage_table.workspace = true +garage_model = { workspace = true, default-features = false, features = ["arbitrary"] } [[bin]] name = "version_crdt" diff --git a/nix/compile.nix b/nix/compile.nix index 799bcc10..da81f093 100644 --- a/nix/compile.nix +++ b/nix/compile.nix @@ -148,6 +148,14 @@ let in rec { toolchain = toolchainFn pkgs; + toolchainNightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { + targets = lib.optionals (target != null) [ rustTarget ]; + extensions = [ + "rust-src" + "rustfmt" + ]; + }); + devShell = pkgs.mkShell { buildInputs = [ toolchain