⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/security/
fuzzing.rs

1//! In-kernel fuzzing infrastructure
2//!
3//! Provides mutation-based fuzzing for security-critical kernel subsystems.
4//! This is a no_std implementation since cargo-fuzz requires std.
5
6use core::sync::atomic::{AtomicU64, Ordering};
7
8/// Fuzzing target trait - implement for each subsystem to fuzz
9pub trait FuzzTarget {
10    /// Name of the fuzz target
11    fn name(&self) -> &'static str;
12
13    /// Run one fuzzing iteration with the given input data
14    fn fuzz(&self, data: &[u8]);
15
16    /// Reset state between iterations if needed
17    fn reset(&self) {}
18}
19
20/// Fuzz runner configuration
21pub struct FuzzConfig {
22    /// Maximum input size in bytes
23    pub max_input_size: usize,
24    /// Number of iterations to run
25    pub max_iterations: u64,
26    /// Seed for the PRNG
27    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
40/// Simple PRNG for mutation (xorshift64)
41struct 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
69/// Mutation strategies for input generation
70enum Mutation {
71    /// Flip random bits
72    BitFlip,
73    /// Replace byte with random value
74    ByteReplace,
75    /// Insert random bytes
76    ByteInsert,
77    /// Delete random bytes
78    ByteDelete,
79    /// Replace with interesting values (0, 0xFF, boundaries)
80    InterestingValues,
81}
82
83/// Mutate input data in-place
84fn mutate(data: &mut [u8], len: &mut usize, max_size: usize, rng: &mut FuzzRng) {
85    if *len == 0 {
86        // Generate initial random data
87        *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                // Shift bytes right
122                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
152/// Fuzzing statistics
153pub 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
171/// Run the fuzzer on a target
172pub 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    // Stack-allocated input buffer (limited to 8KB for kernel stack safety)
177    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 input
183        mutate(&mut input_buf, &mut input_len, max_size, &mut rng);
184
185        // Run target (panics are caught by kernel panic handler)
186        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
204// ============================================================================
205// Built-in fuzz targets
206// ============================================================================
207
208/// ELF parser fuzz target
209pub 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        // Attempt to parse arbitrary bytes as ELF
218        if data.len() >= 64 {
219            let loader = crate::elf::ElfLoader::new();
220            let _ = loader.parse(data);
221        }
222    }
223}
224
225/// Capability token fuzz target
226pub 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            // Exercise token operations
241            let _ = token.id();
242            let _ = token.generation();
243            let _ = token.cap_type();
244            let _ = token.to_u64();
245
246            // Check validity (should not panic)
247            let _ = crate::cap::manager::cap_manager().is_valid(token);
248        }
249    }
250}
251
252/// IPC message fuzz target
253pub 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            // SAFETY: SmallMessage is Copy and repr(C). We read from a byte
263            // buffer that is at least as large as SmallMessage. The data may
264            // contain garbage values but SmallMessage fields are all primitive
265            // types (u64) that accept any bit pattern.
266            let msg = unsafe {
267                core::ptr::read_unaligned(data.as_ptr() as *const crate::ipc::SmallMessage)
268            };
269
270            // Exercise message operations (should not panic)
271            let _ = msg.data;
272        }
273    }
274}
275
276/// Syscall number fuzz target
277pub 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            // Only test the dispatch path, not actual execution
288            let _ = crate::syscall::Syscall::try_from(syscall_num);
289        }
290    }
291}
292
293/// Get fuzzing statistics
294pub fn stats() -> &'static FuzzStats {
295    &FUZZ_STATS
296}
297
298/// Record a crash (called from panic handler hook)
299pub fn record_crash() {
300    FUZZ_STATS.crashes.fetch_add(1, Ordering::Relaxed);
301}