Skip to content

Commit 39e8b41

Browse files
committed
Merge pull-request #441
2 parents 01ef347 + dfe845a commit 39e8b41

File tree

5 files changed

+216
-2
lines changed

5 files changed

+216
-2
lines changed

src/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@ members = [
1111
"qos_p256",
1212
"qos_nsm",
1313
]
14-
exclude = ["init", "qos_aws", "qos_system", "qos_enclave", "eif_build"]
15-
# We need this to avoid issues with the mock feature unintentionally being
14+
exclude = [
15+
"init",
16+
"qos_aws",
17+
"qos_system",
18+
"qos_enclave",
19+
"eif_build",
20+
"qos_p256/fuzz",
21+
"qos_crypto/fuzz",
22+
]
23+
# We need this to avoid issues with the mock feature uinintentionally being
1624
# enabled just because some tests need it.
1725
# https://nickb.dev/blog/cargo-workspace-and-the-feature-unification-pitfall/
1826
resolver = "2"

src/qos_crypto/fuzz/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
name = "qos_crypto_fuzz"
3+
version = "0.0.0"
4+
publish = false
5+
edition = "2021"
6+
7+
[package.metadata]
8+
cargo-fuzz = true
9+
10+
[dependencies]
11+
libfuzzer-sys = "0.4"
12+
arbitrary = { version = "1", features = ["derive"] }
13+
14+
[dependencies.qos_crypto]
15+
path = ".."
16+
17+
# Prevent this from interfering with workspaces
18+
[workspace]
19+
members = ["."]
20+
21+
[profile.release]
22+
# enable arithmetic checks at runtime
23+
overflow-check = 1
24+
25+
[[bin]]
26+
name = "1_shamir_generate_reconstruct"
27+
path = "fuzz_targets/1_shamir_generate_reconstruct.rs"
28+
test = false
29+
doc = false
30+
31+
32+
[[bin]]
33+
name = "2_shamir_input_reconstruct_two_shares"
34+
path = "fuzz_targets/2_shamir_input_reconstruct_two_shares.rs"
35+
test = false
36+
doc = false
37+
38+
[[bin]]
39+
name = "3_shamir_input_reconstruct_three_shares"
40+
path = "fuzz_targets/3_shamir_input_reconstruct_three_shares.rs"
41+
test = false
42+
doc = false
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use qos_crypto::shamir::*;
5+
6+
#[derive(Clone, Debug, arbitrary::Arbitrary)]
7+
pub struct FuzzShamirStruct {
8+
pub n: usize,
9+
pub k: usize,
10+
secret: Box<[u8]>,
11+
}
12+
13+
use std::{convert::TryFrom, iter};
14+
15+
// let the fuzzer control the number of shares, share threshold number, and secret
16+
fuzz_target!(|fuzzerdata: FuzzShamirStruct| {
17+
let n = fuzzerdata.n;
18+
let k = fuzzerdata.k;
19+
let secret = fuzzerdata.secret;
20+
21+
// FUZZER NOTE the effort to reconstruct shares is O(n²) so inputs with a large n
22+
// are particularly slow
23+
24+
// FUZZER TODO artificial limit n to avoid slow inputs, reconsider
25+
if n > 64 {
26+
return;
27+
}
28+
29+
// FUZZER NOTE the shares_generate() function uses RNG internally and is
30+
// therefore non-deterministic, which may limit the reproducibility and effectiveness of this harness
31+
let all_shares_res = shares_generate(&secret, n, k);
32+
33+
match all_shares_res {
34+
Err(_) => {}
35+
Ok(all_shares) => {
36+
// Reconstruct with all the shares
37+
let shares = all_shares.clone();
38+
let reconstructed =
39+
shares_reconstruct(&shares).expect("should succeed");
40+
// expect the reconstruction to work
41+
assert_eq!(secret.to_vec(), reconstructed);
42+
43+
// Reconstruct with enough shares
44+
let shares = &all_shares[..k];
45+
let reconstructed =
46+
shares_reconstruct(shares).expect("should succeed");
47+
48+
// expect the reconstruction to work
49+
assert_eq!(secret.to_vec(), reconstructed);
50+
51+
// Reconstruct with not enough shares
52+
let shares = &all_shares[..(k - 1)];
53+
54+
// although this function returns a Result<>, it does not automatically detect that is has received
55+
// an insufficent number of shares and Err() out - instead, it returns Ok() with an incorrect result
56+
let reconstructed_res = shares_reconstruct(shares);
57+
58+
match reconstructed_res {
59+
// error case is not interesting
60+
Err(_) => {}
61+
// OK case is common
62+
Ok(reconstructed) => {
63+
// if we managed to reconstruct the secret with less than the minimum number of shares
64+
// the something is wrong, or we have a random collision
65+
if reconstructed == secret.to_vec() {
66+
panic!("reconstructed the secret with less than k shares, this should not happen")
67+
}
68+
}
69+
}
70+
}
71+
}
72+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use qos_crypto::shamir::*;
5+
6+
/// let the fuzzer come up with two different shares of arbitrary length
7+
#[derive(Clone, Debug, arbitrary::Arbitrary)]
8+
pub struct FuzzShareReconstruct {
9+
share_one: Box<[u8]>,
10+
share_two: Box<[u8]>,
11+
}
12+
13+
// let the fuzzer control the share data in a two share reconstruction scenario
14+
fuzz_target!(|fuzzerdata: FuzzShareReconstruct| {
15+
let mut shares: Vec<Vec<u8>> = Vec::new();
16+
17+
// FUZZER NOTE the effort to reconstruct shares is O(n²) so inputs with a large n
18+
// are particularly slow
19+
20+
// this construction with three shares covers more edge cases than the two share variant
21+
let mut share_one: Vec<u8> = Vec::new();
22+
let mut share_two: Vec<u8> = Vec::new();
23+
let mut share_three: Vec<u8> = Vec::new();
24+
25+
share_one.extend_from_slice(&fuzzerdata.share_one);
26+
share_two.extend_from_slice(&fuzzerdata.share_two);
27+
28+
shares.push(share_one);
29+
shares.push(share_two);
30+
31+
// Reconstruct with the shares, we expect this to error out often
32+
let reconstructed_res = shares_reconstruct(&shares);
33+
if !reconstructed_res.is_err() {
34+
let _reconstructed = reconstructed_res.unwrap();
35+
36+
// debug print is useful for manual evaluation
37+
// println!("reconstructed {:?}", _reconstructed);
38+
// println!("from shares: {:?}", shares);
39+
// println!("");
40+
}
41+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use qos_crypto::shamir::*;
5+
6+
/// let the fuzzer come up with three different shares of arbitrary length
7+
#[derive(Clone, Debug, arbitrary::Arbitrary)]
8+
pub struct FuzzShareReconstruct {
9+
share_one: Box<[u8]>,
10+
share_two: Box<[u8]>,
11+
share_three: Box<[u8]>,
12+
}
13+
14+
// let the fuzzer control the share data in a three share reconstruction scenario
15+
fuzz_target!(|fuzzerdata: FuzzShareReconstruct| {
16+
let mut shares: Vec<Vec<u8>> = Vec::new();
17+
18+
// note that the effort to reconstruct shares is O(n²) so inputs with a large n
19+
// are particularly slow
20+
// here we have n == 3, so this is not a problem
21+
22+
// this construction with three shares covers more edge cases than the two share variant
23+
let mut share_one: Vec<u8> = Vec::new();
24+
let mut share_two: Vec<u8> = Vec::new();
25+
let mut share_three: Vec<u8> = Vec::new();
26+
27+
share_one.extend_from_slice(&fuzzerdata.share_one);
28+
share_two.extend_from_slice(&fuzzerdata.share_two);
29+
share_three.extend_from_slice(&fuzzerdata.share_three);
30+
31+
// Fuzz workaround for issue in vsss-rs <= 4.3.5
32+
// the bug is fixed in vsss-rs 4.3.6
33+
// if(share_one.len() != share_two.len() ) || (share_one.len() != share_three.len() ) {
34+
// return;
35+
// }
36+
37+
shares.push(share_one);
38+
shares.push(share_two);
39+
shares.push(share_three);
40+
41+
// Reconstruct with the shares, we expect this to error out often
42+
let reconstructed_res = shares_reconstruct(&shares);
43+
if !reconstructed_res.is_err() {
44+
// let _reconstructed = reconstructed_res.unwrap();
45+
46+
// debug print is useful for manual evaluation
47+
// println!("reconstructed {:?}", _reconstructed);
48+
// println!("from shares: {:?}", shares);
49+
// println!("");
50+
}
51+
});

0 commit comments

Comments
 (0)