veridian_kernel/security/
fuzzing.rs1use core::sync::atomic::{AtomicU64, Ordering};
7
8pub trait FuzzTarget {
10 fn name(&self) -> &'static str;
12
13 fn fuzz(&self, data: &[u8]);
15
16 fn reset(&self) {}
18}
19
20pub struct FuzzConfig {
22 pub max_input_size: usize,
24 pub max_iterations: u64,
26 pub seed: u64,
28}
29
30impl Default for FuzzConfig {
31 fn default() -> Self {
32 Self {
33 max_input_size: 4096,
34 max_iterations: 10_000,
35 seed: 0xDEAD_BEEF_CAFE_BABE,
36 }
37 }
38}
39
40struct FuzzRng {
42 state: u64,
43}
44
45impl FuzzRng {
46 fn new(seed: u64) -> Self {
47 Self {
48 state: if seed == 0 { 1 } else { seed },
49 }
50 }
51
52 fn next(&mut self) -> u64 {
53 let mut x = self.state;
54 x ^= x << 13;
55 x ^= x >> 7;
56 x ^= x << 17;
57 self.state = x;
58 x
59 }
60
61 fn next_range(&mut self, max: usize) -> usize {
62 if max == 0 {
63 return 0;
64 }
65 (self.next() as usize) % max
66 }
67}
68
69enum Mutation {
71 BitFlip,
73 ByteReplace,
75 ByteInsert,
77 ByteDelete,
79 InterestingValues,
81}
82
83fn mutate(data: &mut [u8], len: &mut usize, max_size: usize, rng: &mut FuzzRng) {
85 if *len == 0 {
86 *len = core::cmp::min(rng.next_range(64) + 1, max_size);
88 let mut i = 0;
89 while i < *len {
90 data[i] = rng.next() as u8;
91 i += 1;
92 }
93 return;
94 }
95
96 let mutation = match rng.next_range(5) {
97 0 => Mutation::BitFlip,
98 1 => Mutation::ByteReplace,
99 2 => Mutation::ByteInsert,
100 3 => Mutation::ByteDelete,
101 _ => Mutation::InterestingValues,
102 };
103
104 match mutation {
105 Mutation::BitFlip => {
106 if *len > 0 {
107 let pos = rng.next_range(*len);
108 let bit = rng.next_range(8);
109 data[pos] ^= 1u8 << bit;
110 }
111 }
112 Mutation::ByteReplace => {
113 if *len > 0 {
114 let pos = rng.next_range(*len);
115 data[pos] = rng.next() as u8;
116 }
117 }
118 Mutation::ByteInsert => {
119 if *len < max_size {
120 let pos = rng.next_range(*len + 1);
121 let mut i = *len;
123 while i > pos {
124 data[i] = data[i - 1];
125 i -= 1;
126 }
127 data[pos] = rng.next() as u8;
128 *len += 1;
129 }
130 }
131 Mutation::ByteDelete => {
132 if *len > 1 {
133 let pos = rng.next_range(*len);
134 let mut i = pos;
135 while i < *len - 1 {
136 data[i] = data[i + 1];
137 i += 1;
138 }
139 *len -= 1;
140 }
141 }
142 Mutation::InterestingValues => {
143 if *len > 0 {
144 let pos = rng.next_range(*len);
145 let interesting: [u8; 8] = [0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE, 0x41, 0x00];
146 data[pos] = interesting[rng.next_range(interesting.len())];
147 }
148 }
149 }
150}
151
152pub struct FuzzStats {
154 pub iterations: AtomicU64,
155 pub crashes: AtomicU64,
156 pub unique_crashes: AtomicU64,
157}
158
159impl FuzzStats {
160 const fn new() -> Self {
161 Self {
162 iterations: AtomicU64::new(0),
163 crashes: AtomicU64::new(0),
164 unique_crashes: AtomicU64::new(0),
165 }
166 }
167}
168
169static FUZZ_STATS: FuzzStats = FuzzStats::new();
170
171pub fn run_fuzz_target(target: &dyn FuzzTarget, config: &FuzzConfig) -> &'static FuzzStats {
173 let mut rng = FuzzRng::new(config.seed);
174 let max_size = core::cmp::min(config.max_input_size, 8192);
175
176 let mut input_buf = [0u8; 8192];
178 let mut input_len: usize = 0;
179
180 let mut iteration = 0u64;
181 while iteration < config.max_iterations {
182 mutate(&mut input_buf, &mut input_len, max_size, &mut rng);
184
185 target.fuzz(&input_buf[..input_len]);
187
188 FUZZ_STATS.iterations.fetch_add(1, Ordering::Relaxed);
189 target.reset();
190
191 iteration += 1;
192 }
193
194 crate::println!(
195 "[FUZZ] {} completed: {} iterations, {} crashes",
196 target.name(),
197 FUZZ_STATS.iterations.load(Ordering::Relaxed),
198 FUZZ_STATS.crashes.load(Ordering::Relaxed),
199 );
200
201 &FUZZ_STATS
202}
203
204pub struct ElfParserTarget;
210
211impl FuzzTarget for ElfParserTarget {
212 fn name(&self) -> &'static str {
213 "elf_parser"
214 }
215
216 fn fuzz(&self, data: &[u8]) {
217 if data.len() >= 64 {
219 let loader = crate::elf::ElfLoader::new();
220 let _ = loader.parse(data);
221 }
222 }
223}
224
225pub struct CapabilityTokenTarget;
227
228impl FuzzTarget for CapabilityTokenTarget {
229 fn name(&self) -> &'static str {
230 "capability_token"
231 }
232
233 fn fuzz(&self, data: &[u8]) {
234 if data.len() >= 8 {
235 let value = u64::from_le_bytes([
236 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
237 ]);
238 let token = crate::cap::CapabilityToken::from_u64(value);
239
240 let _ = token.id();
242 let _ = token.generation();
243 let _ = token.cap_type();
244 let _ = token.to_u64();
245
246 let _ = crate::cap::manager::cap_manager().is_valid(token);
248 }
249 }
250}
251
252pub struct IpcMessageTarget;
254
255impl FuzzTarget for IpcMessageTarget {
256 fn name(&self) -> &'static str {
257 "ipc_message"
258 }
259
260 fn fuzz(&self, data: &[u8]) {
261 if data.len() >= core::mem::size_of::<crate::ipc::SmallMessage>() {
262 let msg = unsafe {
267 core::ptr::read_unaligned(data.as_ptr() as *const crate::ipc::SmallMessage)
268 };
269
270 let _ = msg.data;
272 }
273 }
274}
275
276pub struct SyscallTarget;
278
279impl FuzzTarget for SyscallTarget {
280 fn name(&self) -> &'static str {
281 "syscall_dispatch"
282 }
283
284 fn fuzz(&self, data: &[u8]) {
285 if data.len() >= 2 {
286 let syscall_num = u16::from_le_bytes([data[0], data[1]]) as usize;
287 let _ = crate::syscall::Syscall::try_from(syscall_num);
289 }
290 }
291}
292
293pub fn stats() -> &'static FuzzStats {
295 &FUZZ_STATS
296}
297
298pub fn record_crash() {
300 FUZZ_STATS.crashes.fetch_add(1, Ordering::Relaxed);
301}