diff --git a/Cargo.lock b/Cargo.lock index 06e65724..2bb5eaf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1551,6 +1551,7 @@ dependencies = [ "garage_db", "garage_model", "garage_table", + "garage_util", "libfuzzer-sys", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 40b0a118..108dccf4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,6 +13,7 @@ libfuzzer-sys = { workspace = true } garage_db.workspace = true garage_table.workspace = true +garage_util.workspace = true garage_model = { workspace = true, default-features = false, features = ["arbitrary"] } [[bin]] @@ -28,3 +29,10 @@ path = "fuzz_targets/mpu_crdt.rs" test = false doc = false bench = false + +[[bin]] +name = "bucket_crdt" +path = "fuzz_targets/bucket_crdt.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/bucket_crdt.rs b/fuzz/fuzz_targets/bucket_crdt.rs new file mode 100644 index 00000000..af94a841 --- /dev/null +++ b/fuzz/fuzz_targets/bucket_crdt.rs @@ -0,0 +1,22 @@ +#![no_main] + +use garage_fuzz::check_crdt_laws; +use garage_model::bucket_table::{Bucket, BucketParams}; +use garage_util::crdt::{self, Deletable}; +use libfuzzer_sys::fuzz_target; + +fn make(state: Deletable) -> Bucket { + Bucket { + id: [0u8; 32].into(), + state, + } +} + +fuzz_target!(|inputs: ( + crdt::Deletable, + crdt::Deletable, + crdt::Deletable +)| { + let (a, b, c) = inputs; + check_crdt_laws(make(a), make(b), make(c)); +}); diff --git a/src/model/bucket_table.rs b/src/model/bucket_table.rs index 0b1766b7..c2a5a135 100644 --- a/src/model/bucket_table.rs +++ b/src/model/bucket_table.rs @@ -63,6 +63,7 @@ mod v08 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct CorsRule { pub id: Option, pub max_age_seconds: Option, @@ -74,6 +75,7 @@ mod v08 { /// Lifecycle configuration rule #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct LifecycleRule { /// The ID of the rule pub id: Option, @@ -91,6 +93,7 @@ mod v08 { /// For each condition, if it is None, it is not verified (always true), /// and if it is Some(x), then it is verified for value x #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct LifecycleFilter { /// If Some(x), object key has to start with prefix x pub prefix: Option, @@ -101,6 +104,7 @@ mod v08 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum LifecycleExpiration { /// Objects expire x days after they were created AfterDays(usize), @@ -109,6 +113,7 @@ mod v08 { } #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct BucketQuotas { /// Maximum size in bytes (bucket size = sum of sizes of objects in the bucket) pub max_size: Option, @@ -139,6 +144,7 @@ mod v2 { /// Configuration for a bucket #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct BucketParams { /// Bucket's creation date pub creation_date: u64, @@ -168,6 +174,7 @@ mod v2 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct WebsiteConfig { pub index_document: String, pub error_document: Option, @@ -178,24 +185,28 @@ mod v2 { } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RedirectAll { pub hostname: String, pub protocol: String, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RoutingRule { pub condition: Option, pub redirect: Redirect, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RedirectCondition { pub http_error_code: Option, pub prefix: Option, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Redirect { pub hostname: Option, pub http_redirect_code: u16, diff --git a/src/model/permission.rs b/src/model/permission.rs index 1eaddf00..ed9d9c68 100644 --- a/src/model/permission.rs +++ b/src/model/permission.rs @@ -6,6 +6,7 @@ use garage_util::crdt::*; /// Permission given to a key in a bucket #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct BucketKeyPerm { /// Timestamp at which the permission was given pub timestamp: u64, diff --git a/src/util/crdt/deletable.rs b/src/util/crdt/deletable.rs index 0594d850..ac64080e 100644 --- a/src/util/crdt/deletable.rs +++ b/src/util/crdt/deletable.rs @@ -4,6 +4,7 @@ use crate::crdt::crdt::*; /// Deletable object (once deleted, cannot go back) #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Deletable { Present(T), Deleted, diff --git a/src/util/crdt/lww.rs b/src/util/crdt/lww.rs index f8b03b85..5dd3c9c8 100644 --- a/src/util/crdt/lww.rs +++ b/src/util/crdt/lww.rs @@ -38,6 +38,7 @@ use crate::crdt::crdt::*; /// This scheme is used by AWS S3 or Soundcloud and often without knowing /// in enterprise when reconciliating databases with ad-hoc scripts. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Lww { ts: u64, v: T, diff --git a/src/util/crdt/lww_map.rs b/src/util/crdt/lww_map.rs index 20a23913..dc1aa0ca 100644 --- a/src/util/crdt/lww_map.rs +++ b/src/util/crdt/lww_map.rs @@ -200,3 +200,19 @@ where Self::new() } } + +#[cfg(feature = "arbitrary")] +impl<'a, K, V> arbitrary::Arbitrary<'a> for LwwMap +where + K: arbitrary::Arbitrary<'a> + Clone + Ord, + V: arbitrary::Arbitrary<'a> + Clone + Crdt, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let map: std::collections::BTreeMap = arbitrary::Arbitrary::arbitrary(u)?; + let mut result = LwwMap::new(); + for (k, (ts, v)) in map { + result.merge_raw(&k, ts, &v); + } + Ok(result) + } +} diff --git a/src/util/crdt/map.rs b/src/util/crdt/map.rs index adac3c38..7fda8e75 100644 --- a/src/util/crdt/map.rs +++ b/src/util/crdt/map.rs @@ -123,3 +123,15 @@ where Self { vals } } } + +#[cfg(feature = "arbitrary")] +impl<'a, K, V> arbitrary::Arbitrary<'a> for Map +where + K: arbitrary::Arbitrary<'a> + Clone + Ord, + V: arbitrary::Arbitrary<'a> + Clone + Crdt, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let map: std::collections::BTreeMap = arbitrary::Arbitrary::arbitrary(u)?; + Ok(map.into_iter().collect()) + } +}