Skip to main content

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}