Closed Box Harnessing
Disabling Compiled-in/Magic Harnesses
Magic start and stop behavior can be disabled, which allows harnessing target software without compiled-in harness code. However, implementation becomes highly target-specific and the magic harness approach is highly preferred.
The same code as before, with no harness:
#include "tsffs.h"
int main() {
char buf[20];
size_t size = sizeof(buf);
function_under_test(buf);
return 0;
}
Can be harnessed in a closed-box fashion by creating a script-branch to wait until the simulation reaches a specified address, timeout, HAP, or any other condition. First, we can disable magic harnesses (this is not strictly necessary unless any magic harnesses actually exist in the target software, but it is good practice).
@tsffs.start_on_harness = False
@tsffs.stop_on_harness = False
Once compiled-in harnesses are disabled, the fuzzing loop can be started manually. There
are two APIs available. Both receive a cpu
object as their first argument, which
should be a processor instance, for example on the QSP platform
qsp.mb.cpu0.core[0][0]
. This is the processor whose associated memory will be written
with new testcases, and whose address space will be used for virtual address
translation.
Both testcases also take a virt: bool
as their final argument, to specify whether the
addresses passed are virtual addresses or physical addresses. If False
, the addresses
will not be translated, which can allow circumventing issues where the address is
accessible, but the page table does not contain an identity mapping for it. A physical
address that is identity mapped may be passed with either a True
or False
value of
virt
.
The first API takes two memory addresses, and is equivalent to the compiled in
HARNESS_START
macro. When called, the fuzzer
will save the passed-in addresses (which may be virtual or physical), read the
pointer-sized integer at *size_address
as the maximum size of testcases. take a
snapshot, and begin the fuzzing loop. Each iteration, a testcase will be written to the
testcase pointer, and the size of the testcase will be written to the provided size
pointer. This API is called like:
@tsffs.iface.fuzz.start(cpu, testcase_address, size_address, True)
The second API takes one memory address and a maximum size. Testcases will be written to the provided testcase address, and will be truncated to the provided maximum size. This API is called like:
@tsffs.iface.fuzz.start_with_maximum_size(cpu, testcase_address, maximum_size, True)
Triggering Manual Stops/Solutions
During manual or harnessed fuzzer execution, a normal stop or solution can be specified at any time using the API. This allows arbitrary conditions for stopping execution of a test case, or treating an execution as a solution, by programming via the SIMICS script or SIMICS Python script.
During execution, the fuzzer can be signaled to stop the current testcase execution with a normal exit (i.e. not a solution), and reset to the initial snapshot with a new testcase with:
@tsffs.iface.fuzz.stop()
Likewise, the fuzzer can be signaled to stop the current testcase execution with a
solution. The fuzzer will save the input for this execution to the solutions directory
(see that section).
The solution
method takes an ID and message that will be saved along with this
solution for later use. Any id and message can be provided, it is entirely up to the
user:
@tsffs.iface.fuzz.solution(1, "A descriptive message about why this is a solution condition")