From 0b9d8266e4b8fb7b7edac4a069d3fb8ac5dbe6b3 Mon Sep 17 00:00:00 2001 From: Volodymyr Babchuk Date: Fri, 14 Mar 2025 17:57:02 +0200 Subject: [PATCH] Fix race between main thread and a vCPU thread (#102) * afl-bridge: fix race between main thread and a vCPU thread In some cases qemu_main_loop() can exit before libafl_sync_exit_cpu() completes. This will case race between Rust code that restarts QEMU and vCPU thread that updates last_exit_reason. What I observed is libafl_exit_signal_vm_start() from a new iteration cleared last_exit_reason.cpu before libafl_sync_exit_cpu() tried to access *last_exit_reason.cpu. This caused NULL pointer dereference. Fix this by not setting cpu->exit in prepare_qemu_exit() and updating it only in rr_cpu_thread_fn() and MTTCG counterpart. This will ensure that qemu_main_loop() waits for vCPU thread to actually stop before returning control to the Rust code. Signed-off-by: Volodymyr Babchuk --------- Signed-off-by: Volodymyr Babchuk Co-authored-by: Romain Malmain --- accel/tcg/tcg-accel-ops-mttcg.c | 11 +++++++++++ accel/tcg/tcg-accel-ops-rr.c | 12 ++++++++++++ libafl/exit.c | 1 - 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/accel/tcg/tcg-accel-ops-mttcg.c b/accel/tcg/tcg-accel-ops-mttcg.c index 49814ec4af..928dd6fdf0 100644 --- a/accel/tcg/tcg-accel-ops-mttcg.c +++ b/accel/tcg/tcg-accel-ops-mttcg.c @@ -56,6 +56,12 @@ static void mttcg_force_rcu(Notifier *notify, void *data) async_run_on_cpu(cpu, do_nothing, RUN_ON_CPU_NULL); } +//// --- Begin LibAFL code --- + +#include "libafl/exit.h" + +//// --- End LibAFL code --- + /* * In the multi-threaded case each vCPU has its own thread. The TLS * variable current_cpu can be used deep in the code to find the @@ -104,6 +110,11 @@ static void *mttcg_cpu_thread_fn(void *arg) * reset by another thread by the time we arrive here. */ break; +//// --- Begin LibAFL code --- + case EXCP_LIBAFL_EXIT: + cpu->stopped = true; + break; +//// --- End LibAFL code --- case EXCP_ATOMIC: bql_unlock(); cpu_exec_step_atomic(cpu); diff --git a/accel/tcg/tcg-accel-ops-rr.c b/accel/tcg/tcg-accel-ops-rr.c index c59c77da4b..5ce6b4596a 100644 --- a/accel/tcg/tcg-accel-ops-rr.c +++ b/accel/tcg/tcg-accel-ops-rr.c @@ -169,6 +169,12 @@ static int rr_cpu_count(void) return cpu_count; } +//// --- Begin LibAFL code --- + +#include "libafl/exit.h" + +//// --- End LibAFL code --- + /* * In the single-threaded case each vCPU is simulated in turn. If * there is more than a single vCPU we create a simple timer to kick @@ -273,6 +279,12 @@ static void *rr_cpu_thread_fn(void *arg) bql_lock(); break; } +//// --- Begin LibAFL code --- + else if (r == EXCP_LIBAFL_EXIT) { + cpu->stopped = true; + break; + } +//// --- End LibAFL code --- } else if (cpu->stop) { if (cpu->unplug) { cpu = CPU_NEXT(cpu); diff --git a/libafl/exit.c b/libafl/exit.c index 4748bba6f9..28464b3715 100644 --- a/libafl/exit.c +++ b/libafl/exit.c @@ -78,7 +78,6 @@ static void prepare_qemu_exit(CPUState* cpu, target_ulong next_pc) #ifndef CONFIG_USER_ONLY qemu_system_debug_request(); - cpu->stopped = true; // TODO check if still needed #endif // in usermode, this may be called from the syscall hook, thus already out