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}