tsffs/interfaces/fuzz.rs
1// Copyright (C) 2024 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5 state::{SolutionKind, StopReason},
6 ManualStartAddress, ManualStartInfo, ManualStartSize, Tsffs,
7};
8use anyhow::{anyhow, Result};
9use libafl::inputs::HasBytesVec;
10use simics::{debug, interface, lookup_file, AsConfObject, AttrValue, ConfObject, GenericAddress};
11use std::{
12 ffi::{c_char, CStr},
13 fs::read,
14};
15
16extern crate ffi2 as ffi;
17
18#[interface(name = "fuzz")]
19impl Tsffs {
20 /// Reproduce a test case execution. This will set the fuzzer's next input through
21 /// one execution using the provided file as input instead of taking input from the
22 /// fuzzer. It will stop execution at the first stop, timeout, or other solution
23 /// instead of continuing the fuzzing loop.
24 ///
25 /// This can be called during configuration *or* after stopping the fuzzer once a solution
26 /// has been found.
27 pub fn repro(&mut self, testcase_file: *mut c_char) -> Result<()> {
28 let simics_path = unsafe { CStr::from_ptr(testcase_file) }.to_str()?;
29
30 let testcase_file = lookup_file(simics_path)?;
31
32 debug!(self.as_conf_object(), "repro({})", testcase_file.display());
33
34 let contents = read(&testcase_file).map_err(|e| {
35 anyhow!(
36 "Failed to read repro testcase file {}: {}",
37 testcase_file.display(),
38 e
39 )
40 })?;
41
42 self.repro_testcase = Some(contents);
43
44 if self.iterations > 0 {
45 // We've done an iteration already, so we need to reset and run
46 self.restore_initial_snapshot()?;
47 self.get_and_write_testcase()?;
48 self.post_timeout_event()?;
49 self.continue_after_repro_prepared()?;
50 }
51
52 Ok(())
53 }
54
55 /// Interface method to manually start the fuzzing loop by taking a snapshot, saving the
56 /// testcase and size address and resuming execution of the simulation. This method does
57 /// not need to be called if `set_start_on_harness` is enabled.
58 ///
59 /// # Arguments
60 ///
61 /// * `cpu` - The CPU whose memory space should be written
62 /// * `testcase_address` - The address to write test cases to
63 /// * `size_address` - The address to write the size of each test case to (optional,
64 /// `max_size` must be given if not provided).
65 ///
66 /// If your target cannot take advantage of the written-back size pointer, use
67 /// `start_with_max_size` instead.
68 pub fn start_with_buffer_ptr_size_ptr(
69 &mut self,
70 cpu: *mut ConfObject,
71 buffer_address: GenericAddress,
72 size_address: GenericAddress,
73 virt: bool,
74 ) -> Result<()> {
75 debug!(
76 self.as_conf_object(),
77 "start({buffer_address:#x}, {size_address:#x})"
78 );
79
80 self.stop_simulation(StopReason::ManualStart {
81 processor: cpu,
82 info: ManualStartInfo {
83 address: if virt {
84 ManualStartAddress::Virtual(buffer_address)
85 } else {
86 ManualStartAddress::Physical(buffer_address)
87 },
88 size: ManualStartSize::SizePtr {
89 address: if virt {
90 ManualStartAddress::Virtual(size_address)
91 } else {
92 ManualStartAddress::Physical(size_address)
93 },
94 },
95 },
96 })?;
97
98 Ok(())
99 }
100
101 /// Interface method to manually start the fuzzing loop by taking a snapshot, saving
102 /// the testcase and maximum testcase size and resuming execution of the simulation.
103 /// This method does not need to be called if `set_start_on_harness` is enabled.
104 ///
105 /// # Arguments
106 ///
107 /// * `cpu` - The CPU whose memory space should be written
108 /// * `testcase_address` - The address to write test cases to
109 /// * `maximum_size` - The maximum size of the test case. The actual size of each test case will
110 /// not be written back to the target software
111 ///
112 /// If your target does not have a buffer readily available to receive testcase data or
113 /// you simply want to use it directly in some other way (e.g. by sending it to a network
114 /// port), use `start_without_buffer`
115 pub fn start_with_buffer_ptr_size_value(
116 &mut self,
117 cpu: *mut ConfObject,
118 testcase_address: GenericAddress,
119 maximum_size: u32,
120 virt: bool,
121 ) -> Result<()> {
122 debug!(
123 self.as_conf_object(),
124 "start_with_maximum_size({testcase_address:#x}, {maximum_size:#x})"
125 );
126
127 self.stop_simulation(StopReason::ManualStart {
128 processor: cpu,
129 info: ManualStartInfo {
130 address: if virt {
131 ManualStartAddress::Virtual(testcase_address)
132 } else {
133 ManualStartAddress::Physical(testcase_address)
134 },
135 size: ManualStartSize::MaxSize(maximum_size.try_into()?),
136 },
137 })?;
138
139 Ok(())
140 }
141
142 /// Interface method to manually start the fuzzing loop by taking a snapshot, saving
143 /// the testcase, size address, and maximum testcase size and resuming execution of the
144 /// simulation. This method does not need to be called if `set_start_on_harness` is enabled.
145 ///
146 /// # Arguments
147 ///
148 /// * `cpu` - The CPU whose memory space should be written
149 /// * `testcase_address` - The address to write test cases to
150 /// * `size_address` - The address to write the size of each test case to
151 /// * `maximum_size` - The maximum size of the test case. The actual size of each test case will
152 /// be written back to the target software at the provided size address.
153 ///
154 /// If your target cannot take advantage of the written-back size pointer, use
155 /// `start_with_max_size` instead.
156 pub fn start_with_buffer_ptr_size_ptr_value(
157 &mut self,
158 cpu: *mut ConfObject,
159 buffer_address: GenericAddress,
160 size_address: GenericAddress,
161 maximum_size: u32,
162 virt: bool,
163 ) -> Result<()> {
164 debug!(
165 self.as_conf_object(),
166 "start({buffer_address:#x}, {size_address:#x}, {maximum_size:#x})"
167 );
168
169 self.stop_simulation(StopReason::ManualStart {
170 processor: cpu,
171 info: ManualStartInfo {
172 address: if virt {
173 ManualStartAddress::Virtual(buffer_address)
174 } else {
175 ManualStartAddress::Physical(buffer_address)
176 },
177 size: ManualStartSize::SizePtrAndMaxSize {
178 address: if virt {
179 ManualStartAddress::Virtual(size_address)
180 } else {
181 ManualStartAddress::Physical(size_address)
182 },
183 maximum_size: maximum_size.try_into()?,
184 },
185 },
186 })?;
187
188 Ok(())
189 }
190
191 /// Interface method to manually start the fuzzing loop by taking a snapshot, saving
192 /// the testcase and maximum testcase size and resuming execution of the simulation.
193 /// This method does not need to be called if `set_start_on_harness` is enabled.
194 ///
195 /// # Arguments
196 ///
197 /// * `cpu` - The CPU to initially trace and post timeout events on. This should typically be
198 /// the CPU that is running the code receiving the input this function returns.
199 ///
200 /// # Return Value
201 ///
202 /// Returns an [`AttrValue`] list of integers. Integers are `u8` sized, in the range 0-255.
203 pub fn start_without_buffer(&mut self, cpu: *mut ConfObject) -> Result<AttrValue> {
204 if !self.have_initial_snapshot() {
205 // Start the fuzzer thread early so we can get a testcase
206 self.start_fuzzer_thread()?;
207 }
208
209 let testcase = self.get_testcase()?;
210
211 self.stop_simulation(StopReason::ManualStartWithoutBuffer { processor: cpu })?;
212
213 Ok(testcase.testcase.bytes().to_vec().try_into()?)
214 }
215
216 /// Interface method to manually signal to stop a testcase execution. When this
217 /// method is called, the current testcase execution will be stopped as if it had
218 /// finished executing normally, and the state will be restored according to
219 /// `snapshot_restore_interval` (restoring every iteration by default). This method is
220 /// particularly useful in callbacks triggered on breakpoints or other complex
221 /// conditions. This method does
222 /// not need to be called if `set_stop_on_harness` is enabled.
223 pub fn stop(&mut self) -> Result<()> {
224 debug!(self.as_conf_object(), "stop");
225
226 self.stop_simulation(StopReason::ManualStop)?;
227
228 Ok(())
229 }
230
231 /// Interface method to manually signal to stop execution with a solution condition.
232 /// When this method is called, the current testcase execution will be stopped as if
233 /// it had finished executing with an exception or timeout, and, if an initial
234 /// snapshot exists, it will be restored before the next iteration resumes.
235 pub fn solution(&mut self, id: u64, message: *mut c_char) -> Result<()> {
236 let message = unsafe { CStr::from_ptr(message) }.to_str()?;
237
238 debug!(self.as_conf_object(), "solution({id:#x}, {message})");
239
240 self.stop_simulation(StopReason::Solution {
241 kind: SolutionKind::Manual,
242 })?;
243
244 Ok(())
245 }
246}