Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add builder and tests for QASAN #2898

Merged
merged 8 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions fuzzers/binary_only/qemu_launcher/Makefile.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
env_scripts = ['''
#!@duckscript
profile = get_env PROFILE

if eq ${profile} "dev"
set_env PROFILE_DIR debug
else
set_env PROFILE_DIR ${profile}
end
''']

[env]
PROFILE = { value = "release", condition = { env_not_set = ["PROFILE"] } }
PROFILE_DIR = { source = "${PROFILE}", default_value = "release", mapping = { "release" = "release", "dev" = "debug" }, condition = { env_not_set = [
Expand Down Expand Up @@ -360,21 +371,11 @@ windows_alias = "unsupported"
script_runner = "@shell"
script = '''
echo "Profile: ${PROFILE}"
cd injection_test || exit 1
make
mkdir in || true
echo aaaaaaaaaa > in/a
timeout 10s "$(find ${TARGET_DIR} -name 'qemu_launcher')" -o out -i in -j ../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then
echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:"
cat fuzz.log
exit 1
else
echo "Fuzzer is working"
fi
make clean
#rm -rf in out fuzz.log || true

export QEMU_LAUNCHER=${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher

./tests/injection/test.sh || exit 1
./tests/qasan/test.sh || exit 1
'''
dependencies = ["build_unix"]

Expand Down
64 changes: 47 additions & 17 deletions fuzzers/binary_only/qemu_launcher/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ impl Client<'_> {

let is_cmplog = self.options.is_cmplog_core(core_id);

let is_drcov = self.options.drcov.is_some();

let extra_tokens = if cfg!(feature = "injections") {
injection_module
.as_ref()
Expand All @@ -109,32 +111,58 @@ impl Client<'_> {
.client_description(client_description)
.extra_tokens(extra_tokens);

if self.options.rerun_input.is_some() && self.options.drcov.is_some() {
// Special code path for re-running inputs with DrCov.
// TODO: Add ASan support, injection support
let drcov = self.options.drcov.as_ref().unwrap();
let drcov = DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build();
instance_builder
.build()
.run(args, tuple_list!(drcov), state)
if self.options.rerun_input.is_some() {
if is_drcov {
// Special code path for re-running inputs with DrCov and Asan.
// TODO: Add injection support
let drcov = self.options.drcov.as_ref().unwrap();

if is_asan {
let modules = tuple_list!(
DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build(),
unsafe { AsanModule::builder().env(&env).asan_report().build() }
);

instance_builder.build().run(args, modules, state)
} else {
let modules = tuple_list!(DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build(),);

instance_builder.build().run(args, modules, state)
}
} else if is_asan {
let modules =
tuple_list!(unsafe { AsanModule::builder().env(&env).asan_report().build() });

instance_builder.build().run(args, modules, state)
} else {
let modules = tuple_list!();

instance_builder.build().run(args, modules, state)
}
} else if is_asan && is_cmplog {
if let Some(injection_module) = injection_module {
instance_builder.build().run(
args,
tuple_list!(
CmpLogModule::default(),
AsanModule::default(&env),
AsanModule::builder().env(&env).build(),
injection_module,
),
state,
)
} else {
instance_builder.build().run(
args,
tuple_list!(CmpLogModule::default(), AsanModule::default(&env),),
tuple_list!(
CmpLogModule::default(),
AsanModule::builder().env(&env).build()
),
state,
)
}
Expand All @@ -160,13 +188,15 @@ impl Client<'_> {
if let Some(injection_module) = injection_module {
instance_builder.build().run(
args,
tuple_list!(AsanModule::default(&env), injection_module),
tuple_list!(AsanModule::builder().env(&env).build(), injection_module),
state,
)
} else {
instance_builder
.build()
.run(args, tuple_list!(AsanModule::default(&env),), state)
instance_builder.build().run(
args,
tuple_list!(AsanModule::builder().env(&env).build()),
state,
)
}
} else if is_asan_guest {
instance_builder
Expand Down
30 changes: 30 additions & 0 deletions fuzzers/binary_only/qemu_launcher/tests/injection/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
set -e

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

if [[ ! -x "$QEMU_LAUNCHER" ]]; then
echo "env variable QEMU_LAUNCHER does not point to a valid executable"
echo "QEMU_LAUNCHER should point to qemu_launcher location, but points to ${QEMU_LAUNCHER} instead."
exit 1
fi

cd "$SCRIPT_DIR"

make

mkdir in || true

echo aaaaaaaaaa > in/a

timeout 10s "$QEMU_LAUNCHER" -o out -i in -j ../../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
if ! grep -Ei "found.*injection" fuzz.log; then
echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:"
cat fuzz.log
exit 1
else
echo "Fuzzer is working"
fi

make clean
7 changes: 7 additions & 0 deletions fuzzers/binary_only/qemu_launcher/tests/qasan/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
all: qasan

qasan: qasan.c
gcc qasan.c -o qasan

clean:
rm -rf qasan out stats.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
D
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
M
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
T
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
U
85 changes: 85 additions & 0 deletions fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// taken from
// https://github.com/AFLplusplus/AFLplusplus/blob/da2d4d8258d725f79c2daa22bf3b1a59c593e472/frida_mode/test/fasan/test.c

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#define UNUSED_PARAMETER(x) (void)(x)

#define LOG(x) \
do { \
char buf[] = x; \
write(STDOUT_FILENO, buf, sizeof(buf)); \
\
} while (false);

void LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *buf = malloc(10);

if (buf == NULL) return;

switch (*data) {
/* Underflow */
case 'U':
LOG("Underflow\n");
buf[-1] = '\0';
free(buf);
break;
/* Overflow */
case 'O':
LOG("Overflow\n");
buf[10] = '\0';
free(buf);
break;
/* Double free */
case 'D':
LOG("Double free\n");
free(buf);
free(buf);
break;
/* Use after free */
case 'A':
LOG("Use after free\n");
free(buf);
buf[0] = '\0';
break;
/* Test Limits (OK) */
case 'T':
LOG("Test-Limits - No Error\n");
buf[0] = 'A';
buf[9] = 'I';
free(buf);
break;
case 'M':
LOG("Memset too many\n");
memset(buf, '\0', 11);
free(buf);
break;
default:
LOG("Nop - No Error\n");
break;
}
}

int main(int argc, char **argv) {
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);

char input = '\0';

// if (read(STDIN_FILENO, &input, 1) < 0) {

// LOG("Failed to read stdin\n");
// return 1;

// }

LLVMFuzzerTestOneInput(&input, 1);

LOG("DONE\n");
return 0;
}
69 changes: 69 additions & 0 deletions fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash
set -e

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

if [[ ! -x "$QEMU_LAUNCHER" ]]; then
echo "env variable QEMU_LAUNCHER does not point to a valid executable"
echo "QEMU_LAUNCHER should point to qemu_launcher"
exit 1
fi

cd "$SCRIPT_DIR"
make

tests=(
"overflow"
"underflow"
"double_free"
"memset"
"uaf"
"test_limits"
)

tests_expected=(
"is 0 bytes to the right of the 10-byte chunk"
"is 1 bytes to the left of the 10-byte chunk"
"is 0 bytes inside the 10-byte chunk"
"is 0 bytes to the right of the 10-byte chunk"
"is 0 bytes inside the 10-byte chunk"
"Test-Limits - No Error"
)

tests_not_expected=(
"dummy"
"dummy"
"dummy"
"dummy"
"dummy"
"Context:"
)

for i in "${!tests[@]}"
do
test="${tests[i]}"
expected="${tests_expected[i]}"
not_expected="${tests_not_expected[i]}"

echo "Running $test detection test..."
OUT=$("$QEMU_LAUNCHER" \
-r "inputs/$test.txt" \
--input dummy \
--output out \
--asan-cores 0 \
-- qasan 2>&1 | tr -d '\0')

if ! echo "$OUT" | grep -q "$expected"; then
echo "ERROR: Expected: $expected."
echo "Output is:"
echo "$OUT"
exit 1
elif echo "$OUT" | grep -q "$not_expected"; then
echo "ERROR: Did not expect: $not_expected."
echo "Output is:"
echo "$OUT"
exit 1
else
echo "OK."
fi
done
5 changes: 3 additions & 2 deletions libafl_qemu/src/modules/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,16 +572,17 @@ where
static mut CALLSTACKS: Option<ThreadLocal<UnsafeCell<Vec<GuestAddr>>>> = None;

#[derive(Debug)]
pub struct FullBacktraceCollector {}
pub struct FullBacktraceCollector;

impl FullBacktraceCollector {
/// # Safety
///
/// This accesses the global [`CALLSTACKS`] variable and may not be called concurrently.
#[expect(rustdoc::private_intra_doc_links)]
pub unsafe fn new() -> Self {
let callstacks_ptr = &raw mut CALLSTACKS;
unsafe { (*callstacks_ptr) = Some(ThreadLocal::new()) };
Self {}
Self
}

pub fn reset(&mut self) {
Expand Down
Loading
Loading