|
5 | 5 | //! `corpus/findings/` instead, which nothing replays, and the search continues past it for a |
6 | 6 | //! clean representative. |
7 | 7 |
|
8 | | -use std::{fs, path::Path}; |
| 8 | +use std::path::Path; |
9 | 9 |
|
10 | | -use arbitrary::{Arbitrary, Unstructured}; |
11 | | -use sqlite3mc_src_fuzz::{run_backup, BackupCase, Cipher}; |
12 | | - |
13 | | -/// A cheap deterministic byte stream, xorshift64*, fast enough for millions of draws. |
14 | | -struct Rng(u64); |
15 | | - |
16 | | -impl Rng { |
17 | | - const fn next_u64(&mut self) -> u64 { |
18 | | - let mut x = self.0; |
19 | | - x ^= x << 13; |
20 | | - x ^= x >> 7; |
21 | | - x ^= x << 17; |
22 | | - self.0 = x; |
23 | | - x.wrapping_mul(0x2545_f491_4f6c_dd1d) |
24 | | - } |
25 | | - |
26 | | - fn bytes(&mut self, len: usize) -> Vec<u8> { |
27 | | - let mut out = Vec::with_capacity(len); |
28 | | - while out.len() < len { |
29 | | - out.extend_from_slice(&self.next_u64().to_le_bytes()); |
30 | | - } |
31 | | - out.truncate(len); |
32 | | - out |
33 | | - } |
34 | | -} |
35 | | - |
36 | | -/// One family to search for, named for the seed file, matched against the decoded case. |
37 | | -struct Family { |
38 | | - name: &'static str, |
39 | | - matches: fn(&BackupCase) -> bool, |
40 | | -} |
| 10 | +use sqlite3mc_src_fuzz::{ |
| 11 | + run_backup, |
| 12 | + seedgen::{run_search, DivergencePolicy, Family, Rng}, |
| 13 | + BackupCase, Cipher, |
| 14 | +}; |
41 | 15 |
|
42 | 16 | const fn has_content(case: &BackupCase) -> bool { |
43 | 17 | !case.src_ops.is_empty() |
44 | 18 | } |
45 | 19 |
|
46 | | -fn families() -> &'static [Family] { |
| 20 | +fn families() -> &'static [Family<BackupCase>] { |
47 | 21 | &[ |
48 | 22 | Family { |
49 | 23 | name: "same-cipher-matched-page-size", |
@@ -94,80 +68,19 @@ fn families() -> &'static [Family] { |
94 | 68 | ] |
95 | 69 | } |
96 | 70 |
|
97 | | -/// Searches for a draw matching `family` that runs clean, saving it to `dir`. The first draw that |
98 | | -/// matches but panics is saved once to `findings_dir` instead, and search continues past it for a |
99 | | -/// clean representative of the family. |
100 | | -fn search_family( |
101 | | - family: &Family, |
102 | | - rng: &mut Rng, |
103 | | - dir: &Path, |
104 | | - findings_dir: &Path, |
105 | | - attempts: usize, |
106 | | -) -> (bool, bool) { |
107 | | - let mut saved_finding = false; |
108 | | - for attempt in 0..attempts { |
109 | | - // Vary the buffer length so arbitrary's Vec/Option draws see different amounts of |
110 | | - // entropy; too short starves ops, too long wastes most attempts on decode failures the |
111 | | - // length check below already prunes. |
112 | | - let len = 64 + (attempt * 37) % 12000; |
113 | | - let bytes = rng.bytes(len); |
114 | | - let Ok(case) = BackupCase::arbitrary_take_rest(Unstructured::new(&bytes)) else { |
115 | | - continue; |
116 | | - }; |
117 | | - if !(family.matches)(&case) { |
118 | | - continue; |
119 | | - } |
120 | | - let result = std::panic::catch_unwind(|| run_backup(&case)); |
121 | | - let Err(payload) = result else { |
122 | | - fs::write(dir.join(family.name), &bytes).expect("corpus seed is writable"); |
123 | | - return (true, saved_finding); |
124 | | - }; |
125 | | - if !saved_finding { |
126 | | - let message = payload |
127 | | - .downcast_ref::<String>() |
128 | | - .map(String::as_str) |
129 | | - .or_else(|| payload.downcast_ref::<&str>().copied()) |
130 | | - .unwrap_or("<non-string panic payload>"); |
131 | | - println!( |
132 | | - "DIVERGENCE family={} case={case:#?}\n{message}", |
133 | | - family.name |
134 | | - ); |
135 | | - fs::write(findings_dir.join(format!("backup-{}", family.name)), &bytes) |
136 | | - .expect("findings directory is writable"); |
137 | | - saved_finding = true; |
138 | | - } |
139 | | - } |
140 | | - (false, saved_finding) |
141 | | -} |
142 | | - |
143 | 71 | fn main() { |
144 | 72 | let dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/backup")); |
145 | | - fs::create_dir_all(dir).expect("the corpus directory can be created"); |
146 | 73 | let findings_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/findings")); |
147 | | - fs::create_dir_all(findings_dir).expect("the findings directory can be created"); |
148 | | - |
149 | | - let mut rng = Rng(0xBACC_0000_BACC_0001); |
150 | | - let attempts_per_family = 400_000usize; |
151 | | - let mut saved = 0usize; |
152 | | - let mut findings = 0usize; |
153 | | - let families = families(); |
154 | | - |
155 | | - for family in families { |
156 | | - let (found, divergence) = |
157 | | - search_family(family, &mut rng, dir, findings_dir, attempts_per_family); |
158 | | - findings += usize::from(divergence); |
159 | | - if found { |
160 | | - saved += 1; |
161 | | - } else { |
162 | | - println!( |
163 | | - "MISS family={} (no clean match in {attempts_per_family} attempts)", |
164 | | - family.name |
165 | | - ); |
166 | | - } |
167 | | - } |
168 | | - |
169 | | - println!( |
170 | | - "saved={saved} families={} findings={findings}", |
171 | | - families.len() |
| 74 | + run_search( |
| 75 | + families(), |
| 76 | + Rng::new(0xBACC_0000_BACC_0001), |
| 77 | + dir, |
| 78 | + 12000, |
| 79 | + 400_000, |
| 80 | + run_backup, |
| 81 | + &DivergencePolicy::QuarantineTo { |
| 82 | + dir: findings_dir, |
| 83 | + prefix: "backup-", |
| 84 | + }, |
172 | 85 | ); |
173 | 86 | } |
0 commit comments