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