iptr_edge_analyzer/control_flow_handler/
fuzz_bitmap.rs1#[cfg(feature = "cache")]
4use std::{num::NonZero, ops::Range};
5
6use crate::{ControlFlowTransitionKind, HandleControlFlow};
7
8pub struct FuzzBitmapControlFlowHandler<M: AsRef<[u8]> + AsMut<[u8]>> {
10 #[cfg(feature = "cache")]
14 per_cache_recorded_bitmap_indices: Vec<u32>,
15 #[cfg(feature = "cache")]
20 per_cache_bitmap: Box<[u8]>,
21 #[cfg(feature = "cache")]
28 bitmap_entries_arena: Vec<CompactBitmapEntry>,
29 fuzzing_bitmap: M,
31 filter_range: Option<Box<[(u64, u64)]>>,
36 prev_loc: u64,
38}
39
40#[cfg(feature = "cache")]
42const INITIAL_RESULTS_PER_CACHE: usize = 64;
43#[cfg(feature = "cache")]
45const INITIAL_BITMAP_ENTRIES_ARENA_SIZE: usize = 0x100;
46
47impl<M: AsRef<[u8]> + AsMut<[u8]>> FuzzBitmapControlFlowHandler<M> {
48 pub fn new(fuzzing_bitmap: M, filter_range: Option<&[(u64, u64)]>) -> Self {
55 #[cfg(feature = "cache")]
56 let bitmap_size = fuzzing_bitmap.as_ref().len();
57 #[cfg(feature = "cache")]
58 let mut bitmap_entries_arena = Vec::with_capacity(INITIAL_BITMAP_ENTRIES_ARENA_SIZE);
59 #[cfg(feature = "cache")]
60 bitmap_entries_arena.push(DUMMY_BITMAP_ENTRY);
61 Self {
62 #[cfg(feature = "cache")]
63 per_cache_recorded_bitmap_indices: Vec::with_capacity(INITIAL_RESULTS_PER_CACHE),
64 #[cfg(feature = "cache")]
65 per_cache_bitmap: vec![0u8; bitmap_size].into_boxed_slice(),
66 #[cfg(feature = "cache")]
67 bitmap_entries_arena,
68 filter_range: filter_range.map(Box::from),
69 fuzzing_bitmap,
70 prev_loc: 0,
71 }
72 }
73
74 #[inline]
75 fn is_addr_in_filter_range(&self, address: u64) -> bool {
76 let Some(filter_range) = &self.filter_range else {
77 return true;
78 };
79 for (start, end) in filter_range {
80 if (*start..=*end).contains(&address) {
81 return true;
82 }
83 }
84 false
85 }
86
87 fn bitmap_size_modulus(&self) -> u64 {
89 self.fuzzing_bitmap.as_ref().len() as u64
90 }
91
92 #[expect(clippy::cast_possible_truncation)]
94 fn on_new_loc(&mut self, new_loc: u64) -> usize {
95 let bitmap_index = self.prev_loc ^ new_loc;
96 self.set_new_loc(new_loc);
97 (bitmap_index % self.bitmap_size_modulus()) as usize
98 }
99
100 fn set_new_loc(&mut self, new_loc: u64) {
102 self.prev_loc = new_loc >> 1;
103 }
104
105 pub fn diagnose(&self) -> FuzzBitmapDiagnosticInformation {
107 FuzzBitmapDiagnosticInformation {
108 #[cfg(feature = "cache")]
109 bitmap_entries_count: self.bitmap_entries_arena.len(),
110 }
111 }
112}
113
114pub struct FuzzBitmapDiagnosticInformation {
118 #[cfg(feature = "cache")]
120 pub bitmap_entries_count: usize,
121}
122
123impl<M: AsRef<[u8]> + AsMut<[u8]>> HandleControlFlow for FuzzBitmapControlFlowHandler<M> {
124 type Error = std::convert::Infallible;
125 #[cfg(feature = "cache")]
126 type CachedKey = PerCacheBitmapEntries;
127
128 fn at_decode_begin(&mut self) -> Result<(), Self::Error> {
129 self.prev_loc = 0;
130 #[cfg(feature = "cache")]
131 self.clear_current_cache();
132 Ok(())
133 }
134
135 #[inline]
136 #[cfg_attr(feature = "cache", expect(clippy::cast_possible_truncation))]
137 #[expect(clippy::enum_glob_use)]
138 fn on_new_block(
139 &mut self,
140 block_addr: u64,
141 transition_kind: ControlFlowTransitionKind,
142 cache: bool,
143 ) -> Result<(), Self::Error> {
144 use ControlFlowTransitionKind::*;
145 if !self.is_addr_in_filter_range(block_addr) {
146 self.set_new_loc(0);
147 return Ok(());
148 }
149 match transition_kind {
150 ConditionalBranch | Indirect | DirectJump | DirectCall => {
151 let bitmap_index = self.on_new_loc(block_addr);
152 debug_assert!(
153 bitmap_index < self.fuzzing_bitmap.as_ref().len(),
154 "Unexpected OOB"
155 );
156 let count = unsafe { self.fuzzing_bitmap.as_mut().get_unchecked_mut(bitmap_index) };
157 *count = count.wrapping_add(1);
158 #[cfg(feature = "cache")]
159 if cache {
160 debug_assert!(bitmap_index < self.per_cache_bitmap.len(), "Unexpected OOB");
162 let count = unsafe { self.per_cache_bitmap.get_unchecked_mut(bitmap_index) };
163 if *count == 0 {
164 debug_assert!(u32::try_from(bitmap_index).is_ok(), "Bitmap size too large");
165 self.per_cache_recorded_bitmap_indices
166 .push(bitmap_index as u32);
167 }
168 *count = count.wrapping_add(1);
169 }
170 #[cfg(not(feature = "cache"))]
171 let _ = cache;
172 }
173 NewBlock => {
174 self.set_new_loc(block_addr);
175 }
176 }
177 Ok(())
178 }
179
180 #[cfg(feature = "cache")]
181 #[expect(clippy::cast_possible_truncation)]
182 fn cache_prev_cached_key(&mut self, cached_key: Self::CachedKey) -> Result<(), Self::Error> {
183 let entries_range = cached_key.to_range();
184 debug_assert!(
186 entries_range.end <= self.bitmap_entries_arena.len(),
187 "Unexpected OOB"
188 );
189 let bitmap_entries = unsafe { self.bitmap_entries_arena.get_unchecked(entries_range) };
190 for bitmap_entry in bitmap_entries {
191 let bitmap_index = bitmap_entry.bitmap_index();
192 debug_assert!(bitmap_index < self.per_cache_bitmap.len(), "Unexpected OOB");
194 let count = unsafe { self.per_cache_bitmap.get_unchecked_mut(bitmap_index) };
195 if *count == 0 {
196 debug_assert!(u32::try_from(bitmap_index).is_ok(), "Bitmap size too large");
197 self.per_cache_recorded_bitmap_indices
198 .push(bitmap_index as u32);
199 }
200 *count = count.wrapping_add(bitmap_entry.bitmap_count());
201 }
202
203 Ok(())
204 }
205
206 #[cfg(feature = "cache")]
207 fn clear_current_cache(&mut self) -> Result<(), Self::Error> {
208 for bitmap_index in self.per_cache_recorded_bitmap_indices.drain(..) {
209 let bitmap_index = bitmap_index as usize;
210 debug_assert!(bitmap_index < self.per_cache_bitmap.len(), "Unexpected OOB");
212 let count = unsafe { self.per_cache_bitmap.get_unchecked_mut(bitmap_index) };
213 *count = 0;
214 }
215 Ok(())
216 }
217
218 #[cfg(feature = "cache")]
219 #[expect(clippy::cast_possible_truncation)]
220 fn take_cache(&mut self) -> Result<Option<Self::CachedKey>, Self::Error> {
221 let start_index = self.bitmap_entries_arena.len();
222 for bitmap_index in self.per_cache_recorded_bitmap_indices.drain(..) {
223 let bitmap_index = bitmap_index as usize;
224 debug_assert!(bitmap_index < self.per_cache_bitmap.len(), "Unexpected OOB");
226 let count = unsafe { self.per_cache_bitmap.get_unchecked_mut(bitmap_index) };
227
228 let bitmap_entry = CompactBitmapEntry::new(bitmap_index, *count);
229 self.bitmap_entries_arena.push(bitmap_entry);
230
231 *count = 0;
232 }
233 let end_index = self.bitmap_entries_arena.len();
234 if start_index == end_index {
235 return Ok(None);
237 }
238 debug_assert!(start_index > 0 && end_index > 0, "Unexpected!");
240 debug_assert!(
241 u32::try_from(start_index).is_ok() && u32::try_from(end_index).is_ok(),
242 "Too many bitmap entries!"
243 );
244 let start_index = unsafe { NonZero::new_unchecked(start_index as u32) };
245 let end_index = unsafe { NonZero::new_unchecked(end_index as u32) };
246
247 Ok(Some(PerCacheBitmapEntries {
248 start: start_index,
249 end: end_index,
250 }))
251 }
252
253 #[cfg(feature = "cache")]
254 fn on_reused_cache(
255 &mut self,
256 cached_key: &Self::CachedKey,
257 new_bb: u64,
258 ) -> Result<(), Self::Error> {
259 let entries_range = cached_key.to_range();
260 debug_assert!(
262 entries_range.end <= self.bitmap_entries_arena.len(),
263 "Unexpected OOB"
264 );
265 let bitmap_entries = unsafe { self.bitmap_entries_arena.get_unchecked(entries_range) };
266 for bitmap_entry in bitmap_entries {
268 let bitmap_index = bitmap_entry.bitmap_index();
269 debug_assert!(
270 bitmap_index < self.fuzzing_bitmap.as_ref().len(),
271 "Unexpected OOB"
272 );
273 let count = unsafe { self.fuzzing_bitmap.as_mut().get_unchecked_mut(bitmap_index) };
274 *count = count.wrapping_add(bitmap_entry.bitmap_count());
275 }
276 self.set_new_loc(new_bb);
277
278 Ok(())
279 }
280}
281
282#[cfg(feature = "cache")]
285const DUMMY_BITMAP_ENTRY: CompactBitmapEntry = CompactBitmapEntry { value: 0 };
286
287#[cfg(feature = "cache")]
289#[derive(Clone, Copy)]
290struct CompactBitmapEntry {
291 value: u32,
295}
296
297#[cfg(feature = "cache")]
298impl CompactBitmapEntry {
299 #[expect(clippy::cast_possible_truncation)]
302 fn new(bitmap_index: usize, bitmap_count: u8) -> Self {
303 debug_assert!(bitmap_index <= 0x00FF_FFFF, "Bitmap size too large");
304 let bitmap_index = (bitmap_index as u32) << 8;
305 Self {
306 value: bitmap_index | (bitmap_count as u32),
307 }
308 }
309
310 fn bitmap_index(self) -> usize {
312 (self.value >> 8) as usize
313 }
314
315 #[expect(clippy::cast_possible_truncation)]
317 fn bitmap_count(self) -> u8 {
318 self.value as u8
319 }
320}
321
322#[cfg(feature = "cache")]
326#[doc(hidden)]
327#[derive(Clone, Copy)]
328pub struct PerCacheBitmapEntries {
329 start: NonZero<u32>,
331 end: NonZero<u32>,
333}
334
335#[cfg(feature = "cache")]
336impl PerCacheBitmapEntries {
337 fn to_range(self) -> Range<usize> {
339 (self.start.get() as usize)..(self.end.get() as usize)
340 }
341}