Skip to content

Commit 29f048c

Browse files
authored
Add metamorphic fuzzer for lthash (#1334)
1 parent 6b2c76d commit 29f048c

2 files changed

Lines changed: 173 additions & 0 deletions

File tree

cryptography/fuzz/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,10 @@ path = "fuzz_targets/bls12381_tle.rs"
109109
test = false
110110
doc = false
111111
bench = false
112+
113+
[[bin]]
114+
name = "metamorph_lthash"
115+
path = "fuzz_targets/metamorph_lthash.rs"
116+
test = false
117+
doc = false
118+
bench = false
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#![no_main]
2+
3+
use arbitrary::{Arbitrary, Unstructured};
4+
use commonware_cryptography::lthash::LtHash;
5+
use libfuzzer_sys::fuzz_target;
6+
use rand::{rngs::StdRng, Rng, SeedableRng};
7+
8+
#[derive(Debug, Arbitrary)]
9+
enum MutationType {
10+
AddThenSubtractSameValue,
11+
AddCombinedThenSubtractIndividual,
12+
SubtractCombinedThenAddIndividual,
13+
AddMultipleElementsThenSubtractSum,
14+
AddSubtractCommutativitySum,
15+
}
16+
17+
#[derive(Debug)]
18+
struct FuzzInput {
19+
seed: u64,
20+
initial_value: Vec<u8>,
21+
mutation_type: MutationType,
22+
additional_value_a: Vec<u8>,
23+
additional_value_b: Vec<u8>,
24+
num_elements: u8,
25+
}
26+
27+
impl<'a> Arbitrary<'a> for FuzzInput {
28+
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
29+
Ok(FuzzInput {
30+
seed: u.arbitrary()?,
31+
initial_value: u.arbitrary()?,
32+
mutation_type: u.arbitrary()?,
33+
additional_value_a: u.arbitrary()?,
34+
additional_value_b: u.arbitrary()?,
35+
num_elements: u.arbitrary()?,
36+
})
37+
}
38+
}
39+
40+
fn generate_random_bytes(rng: &mut StdRng, min_len: usize, max_len: usize) -> Vec<u8> {
41+
let len = rng.gen_range(min_len..=max_len);
42+
let mut bytes = vec![0u8; len];
43+
rng.fill(&mut bytes[..]);
44+
bytes
45+
}
46+
47+
fn fuzz(input: FuzzInput) {
48+
let mut rng = StdRng::seed_from_u64(input.seed);
49+
50+
let mut lthash = LtHash::new();
51+
52+
lthash.add(&input.initial_value);
53+
let initial_checksum = lthash.checksum();
54+
55+
match input.mutation_type {
56+
MutationType::AddThenSubtractSameValue => {
57+
let value = generate_random_bytes(&mut rng, 1, 1024);
58+
lthash.add(&value);
59+
lthash.subtract(&value);
60+
}
61+
MutationType::AddCombinedThenSubtractIndividual => {
62+
let a = &input.additional_value_a;
63+
let b = &input.additional_value_b;
64+
65+
// Create hash(a+b) by combining hash(a) and hash(b)
66+
let mut hash_a = LtHash::new();
67+
hash_a.add(a);
68+
69+
let mut hash_b = LtHash::new();
70+
hash_b.add(b);
71+
72+
let mut hash_a_plus_b = LtHash::new();
73+
hash_a_plus_b.combine(&hash_a);
74+
hash_a_plus_b.combine(&hash_b);
75+
76+
// Add H(a+b) then subtract H(a) and H(b)
77+
lthash.combine(&hash_a_plus_b);
78+
79+
let mut neg_hash_a = LtHash::new();
80+
neg_hash_a.subtract(a);
81+
lthash.combine(&neg_hash_a);
82+
83+
let mut neg_hash_b = LtHash::new();
84+
neg_hash_b.subtract(b);
85+
lthash.combine(&neg_hash_b);
86+
}
87+
MutationType::SubtractCombinedThenAddIndividual => {
88+
let a = &input.additional_value_a;
89+
let b = &input.additional_value_b;
90+
91+
// Create hash(a+b) by combining hash(a) and hash(b)
92+
let mut hash_a = LtHash::new();
93+
hash_a.add(a);
94+
95+
let mut hash_b = LtHash::new();
96+
hash_b.add(b);
97+
98+
let mut hash_a_plus_b = LtHash::new();
99+
hash_a_plus_b.combine(&hash_a);
100+
hash_a_plus_b.combine(&hash_b);
101+
102+
// Subtract H(a+b) then add H(a) and H(b)
103+
let mut neg_hash_a_plus_b = LtHash::new();
104+
neg_hash_a_plus_b.subtract(a);
105+
neg_hash_a_plus_b.subtract(b);
106+
lthash.combine(&neg_hash_a_plus_b);
107+
108+
lthash.combine(&hash_a);
109+
lthash.combine(&hash_b);
110+
}
111+
MutationType::AddMultipleElementsThenSubtractSum => {
112+
let num_elements = (input.num_elements as usize % 32) + 1;
113+
let mut elements = Vec::new();
114+
let mut sum_hash = LtHash::new();
115+
116+
// Add random elements and collect them
117+
for _ in 0..num_elements {
118+
let element = generate_random_bytes(&mut rng, 1, 256);
119+
lthash.add(&element);
120+
sum_hash.add(&element);
121+
elements.push(element);
122+
}
123+
124+
// Subtract the sum of all elements
125+
let mut neg_sum = LtHash::new();
126+
for element in &elements {
127+
neg_sum.subtract(element);
128+
}
129+
lthash.combine(&neg_sum);
130+
}
131+
MutationType::AddSubtractCommutativitySum => {
132+
// Generate n random values
133+
let num_elements = (input.num_elements as usize % 128) + 2; // At least 2 elements
134+
let mut elements = Vec::new();
135+
136+
for _ in 0..num_elements {
137+
let element = generate_random_bytes(&mut rng, 1, 256);
138+
elements.push(element);
139+
}
140+
141+
// Add elements in original order
142+
for element in &elements {
143+
lthash.add(element);
144+
}
145+
146+
// Shuffle elements
147+
let mut shuffled_elements = elements.clone();
148+
for i in (1..shuffled_elements.len()).rev() {
149+
let j = rng.gen_range(0..=i);
150+
shuffled_elements.swap(i, j);
151+
}
152+
153+
// Subtract elements in shuffled order
154+
for element in &shuffled_elements {
155+
lthash.subtract(element);
156+
}
157+
}
158+
}
159+
160+
let final_checksum = lthash.checksum();
161+
assert_eq!(initial_checksum, final_checksum, "Final check failed");
162+
}
163+
164+
fuzz_target!(|input: FuzzInput| {
165+
fuzz(input);
166+
});

0 commit comments

Comments
 (0)