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 <volodymyr_babchuk@epam.com>

---------

Signed-off-by: Volodymyr Babchuk <volodymyr_babchuk@epam.com>
Co-authored-by: Romain Malmain <romain.malmain@pm.me>
This commit is contained in:
Volodymyr Babchuk 2025-03-14 17:57:02 +02:00 committed by GitHub
parent a86bd6bbcb
commit 0b9d8266e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 23 additions and 1 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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