Skip to content

Commit

Permalink
Implement user-space QEMU ASAN
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Apr 7, 2024
1 parent 527b892 commit 8eab524
Show file tree
Hide file tree
Showing 18 changed files with 817 additions and 70 deletions.
39 changes: 37 additions & 2 deletions fuzzers/qemu_launcher/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,7 @@ ${CROSS_CXX} \
-I"${TARGET_DIR}/build-zlib/zlib/include" \
-L"${TARGET_DIR}/build-zlib/zlib/lib" \
-o"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}" \
-lm \
-static
-lm
'''
dependencies = [ "libpng" ]

Expand Down Expand Up @@ -280,6 +279,42 @@ args = [
]
dependencies = [ "harness", "fuzzer" ]

[tasks.asan]
linux_alias = "asan_unix"
mac_alias = "unsupported"
windows_alias = "unsupported"

[tasks.asan_unix]
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher-${CARGO_MAKE_PROFILE}"
args = [
"--input", "./corpus",
"--output", "${TARGET_DIR}/output/",
"--log", "${TARGET_DIR}/output/log.txt",
"--cores", "0",
"--asan-cores", "0",
"--",
"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}",
]
dependencies = [ "harness", "fuzzer" ]

[tasks.asan_guest]
linux_alias = "asan_guest_unix"
mac_alias = "unsupported"
windows_alias = "unsupported"

[tasks.asan_guest_unix]
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher-${CARGO_MAKE_PROFILE}"
args = [
"--input", "./corpus",
"--output", "${TARGET_DIR}/output/",
"--log", "${TARGET_DIR}/output/log.txt",
"--cores", "0",
"--asan-guest-cores", "0",
"--",
"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}",
]
dependencies = [ "harness", "fuzzer" ]

[tasks.test]
linux_alias = "test_unix"
mac_alias = "unsupported"
Expand Down
66 changes: 61 additions & 5 deletions fuzzers/qemu_launcher/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list};
use libafl_qemu::injections::QemuInjectionHelper;
use libafl_qemu::{
asan::{init_qemu_with_asan, QemuAsanHelper},
asan_guest::{init_qemu_with_asan_guest, QemuAsanGuestHelper},
cmplog::QemuCmpLogHelper,
edges::QemuEdgeCoverageHelper,
elf::EasyElf,
Expand Down Expand Up @@ -110,12 +111,22 @@ impl<'a> Client<'a> {
let mut env = self.env();
log::debug!("ENV: {:#?}", env);

let (qemu, mut asan) = {
if self.options.is_asan_core(core_id) {
let is_asan = self.options.is_asan_core(core_id);
let is_asan_guest = self.options.is_asan_guest_core(core_id);

if is_asan && is_asan_guest {
Err(Error::empty_optional("Multiple ASAN modes configured"))?;
}

let (qemu, mut asan, mut asan_lib) = {
if is_asan {
let (emu, asan) = init_qemu_with_asan(&mut args, &mut env)?;
(emu, Some(asan))
(emu, Some(asan), None)
} else if is_asan_guest {
let (emu, asan_lib) = init_qemu_with_asan_guest(&mut args, &mut env)?;
(emu, None, Some(asan_lib))
} else {
(Qemu::init(&args, &env)?, None)
(Qemu::init(&args, &env)?, None, None)
}
};

Expand Down Expand Up @@ -151,7 +162,6 @@ impl<'a> Client<'a> {
log::debug!("ret_addr = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr);

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

let edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&qemu)?);
Expand Down Expand Up @@ -203,6 +213,52 @@ impl<'a> Client<'a> {
state,
)
}
} else if is_asan_guest && is_cmplog {
if let Some(injection_helper) = injection_helper {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap()),
injection_helper
),
state,
)
} else {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap()),
),
state,
)
}
} else if is_asan {
if let Some(injection_helper) = injection_helper {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuAsanHelper::default(asan.take().unwrap()),
injection_helper
),
state,
)
} else {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuAsanHelper::default(asan.take().unwrap()),
),
state,
)
}
} else if is_asan_guest {
let helpers = tuple_list!(
edge_coverage_helper,
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap())
);
instance.build().run(helpers, state)
} else if is_cmplog {
if let Some(injection_helper) = injection_helper {
instance.build().run(
Expand Down
9 changes: 9 additions & 0 deletions fuzzers/qemu_launcher/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ pub struct FuzzerOptions {
#[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)]
pub asan_cores: Option<Cores>,

#[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)]
pub asan_guest_cores: Option<Cores>,

#[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)]
pub cmplog_cores: Option<Cores>,

Expand Down Expand Up @@ -113,6 +116,12 @@ impl FuzzerOptions {
.map_or(false, |c| c.contains(core_id))
}

pub fn is_asan_guest_core(&self, core_id: CoreId) -> bool {
self.asan_guest_cores
.as_ref()
.map_or(false, |c| c.contains(core_id))
}

pub fn is_cmplog_core(&self, core_id: CoreId) -> bool {
self.cmplog_cores
.as_ref()
Expand Down
3 changes: 2 additions & 1 deletion libafl_qemu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ features = ["document-features", "default", "x86_64", "usermode"]
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["fork", "build_libqasan", "serdeany_autoreg", "injections"]
default = ["fork", "build_libgasan", "build_libqasan", "serdeany_autoreg", "injections"]
clippy = [] # special feature for clippy, don't use in normal projects§
document-features = ["dep:document-features"]

Expand All @@ -27,6 +27,7 @@ injections = ["serde_yaml", "toml"]
## Fork support
fork = ["libafl/fork"]
## Build libqasan for address sanitization
build_libgasan = []
build_libqasan = []

#! ## The following architecture features are mutually exclusive.
Expand Down
6 changes: 3 additions & 3 deletions libafl_qemu/build_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub fn build() {

let libafl_qemu_hdr_name = "libafl_qemu.h";

let build_libgasan = cfg!(all(feature = "build_libgasan", not(feature = "hexagon")));
let build_libqasan = cfg!(all(feature = "build_libqasan", not(feature = "hexagon")));

let exit_hdr_dir = PathBuf::from("runtime");
Expand Down Expand Up @@ -50,7 +51,7 @@ pub fn build() {
println!("cargo:rerun-if-env-changed=CPU_TARGET");
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");

let cross_cc = if (emulation_mode == "usermode") && build_libqasan {
let cross_cc = if (emulation_mode == "usermode") && (build_libgasan || build_libqasan) {
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)
let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| {
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH);
Expand Down Expand Up @@ -96,7 +97,7 @@ pub fn build() {
.write_to_file(binding_file)
.expect("Could not write bindings.");

if (emulation_mode == "usermode") && build_libqasan {
if (emulation_mode == "usermode") && (build_libgasan || build_libqasan) {
let qasan_dir = Path::new("libqasan");
let qasan_dir = fs::canonicalize(qasan_dir).unwrap();
println!("cargo:rerun-if-changed={}", qasan_dir.display());
Expand All @@ -114,6 +115,5 @@ pub fn build() {
.status()
.expect("make failed")
.success());
// println!("cargo:rerun-if-changed={}/libqasan.so", target_dir.display());
}
}
2 changes: 1 addition & 1 deletion libafl_qemu/libafl_qemu_build/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use which::which;

const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "f282d6aef5e28421255293ebbb52d835281f2730";
const QEMU_REVISION: &str = "fd6a2f3cce4b0de2ad48703f7c93f9813c96b12c";

pub struct BuildResult {
pub qemu_path: PathBuf,
Expand Down
3 changes: 0 additions & 3 deletions libafl_qemu/libafl_qemu_sys/build_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ pub fn build() {
println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\"");
println!("cargo:rerun-if-env-changed=EMULATION_MODE");

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=build_linux.rs");

// Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :)
assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon");
Expand Down
18 changes: 16 additions & 2 deletions libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13500,7 +13500,14 @@ extern "C" {
}
extern "C" {
pub fn libafl_add_read_hook(
gen: ::std::option::Option<extern "C" fn(data: u64, pc: target_ulong, oi: MemOpIdx) -> u64>,
gen: ::std::option::Option<
unsafe extern "C" fn(
data: u64,
pc: target_ulong,
addr: *mut TCGTemp,
oi: MemOpIdx,
) -> u64,
>,
exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
Expand All @@ -13513,7 +13520,14 @@ extern "C" {
}
extern "C" {
pub fn libafl_add_write_hook(
gen: ::std::option::Option<extern "C" fn(data: u64, pc: target_ulong, oi: MemOpIdx) -> u64>,
gen: ::std::option::Option<
unsafe extern "C" fn(
data: u64,
pc: target_ulong,
addr: *mut TCGTemp,
oi: MemOpIdx,
) -> u64,
>,
exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
Expand Down
8 changes: 6 additions & 2 deletions libafl_qemu/libqasan/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

OUT_DIR ?= .

override CFLAGS += -Wno-int-to-void-pointer-cast -ggdb -O1 -fno-builtin
override CFLAGS += -Wno-int-to-void-pointer-cast -ggdb -O1 -fno-builtin -Wno-unused-result
override LDFLAGS += -ldl -pthread

SRC := libqasan.c hooks.c malloc.c string.c uninstrument.c patch.c dlmalloc.c printf/printf.c
HDR := libqasan.h qasan.h map_macro.h printf/printf.h

all: $(OUT_DIR)/libqasan.so
all: $(OUT_DIR)/libgasan.so $(OUT_DIR)/libqasan.so

$(OUT_DIR)/libgasan.so: $(HDR) $(SRC)
$(CC) $(CFLAGS) -DASAN_GUEST=1 -fPIC -shared $(SRC) -o $@ $(LDFLAGS)

$(OUT_DIR)/libqasan.so: $(HDR) $(SRC)
$(CC) $(CFLAGS) -fPIC -shared $(SRC) -o $@ $(LDFLAGS)
Expand All @@ -30,4 +33,5 @@ $(OUT_DIR)/libqasan.so: $(HDR) $(SRC)

clean:
rm -f *.o *.so *~ a.out core core.[1-9][0-9]*
rm -f $(OUT_DIR)/libgasan.so
rm -f $(OUT_DIR)/libqasan.so
Loading

0 comments on commit 8eab524

Please sign in to comment.