Fuzz Bucket CRDT (#1442)

Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1442
Reviewed-by: Alex <lx@deuxfleurs.fr>
This commit is contained in:
Arthur Carcano
2026-05-06 18:55:47 +00:00
committed by Alex
parent 57ceed38f3
commit 0da317e3d5
9 changed files with 73 additions and 0 deletions
Generated
+1
View File
@@ -1551,6 +1551,7 @@ dependencies = [
"garage_db",
"garage_model",
"garage_table",
"garage_util",
"libfuzzer-sys",
]
+8
View File
@@ -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
+22
View File
@@ -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<BucketParams>) -> Bucket {
Bucket {
id: [0u8; 32].into(),
state,
}
}
fuzz_target!(|inputs: (
crdt::Deletable<BucketParams>,
crdt::Deletable<BucketParams>,
crdt::Deletable<BucketParams>
)| {
let (a, b, c) = inputs;
check_crdt_laws(make(a), make(b), make(c));
});
+11
View File
@@ -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<String>,
pub max_age_seconds: Option<u64>,
@@ -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<String>,
@@ -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<String>,
@@ -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<u64>,
@@ -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<String>,
@@ -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<RedirectCondition>,
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<u16>,
pub prefix: Option<String>,
}
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Redirect {
pub hostname: Option<String>,
pub http_redirect_code: u16,
+1
View File
@@ -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,
+1
View File
@@ -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<T> {
Present(T),
Deleted,
+1
View File
@@ -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<T> {
ts: u64,
v: T,
+16
View File
@@ -200,3 +200,19 @@ where
Self::new()
}
}
#[cfg(feature = "arbitrary")]
impl<'a, K, V> arbitrary::Arbitrary<'a> for LwwMap<K, V>
where
K: arbitrary::Arbitrary<'a> + Clone + Ord,
V: arbitrary::Arbitrary<'a> + Clone + Crdt,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let map: std::collections::BTreeMap<K, (u64, V)> = arbitrary::Arbitrary::arbitrary(u)?;
let mut result = LwwMap::new();
for (k, (ts, v)) in map {
result.merge_raw(&k, ts, &v);
}
Ok(result)
}
}
+12
View File
@@ -123,3 +123,15 @@ where
Self { vals }
}
}
#[cfg(feature = "arbitrary")]
impl<'a, K, V> arbitrary::Arbitrary<'a> for Map<K, V>
where
K: arbitrary::Arbitrary<'a> + Clone + Ord,
V: arbitrary::Arbitrary<'a> + Clone + Crdt,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let map: std::collections::BTreeMap<K, V> = arbitrary::Arbitrary::arbitrary(u)?;
Ok(map.into_iter().collect())
}
}