Hi,
I observed a real crash misclassification in kafl.qemu that appears to come from the upstream result handoff logic.
This is not only a source-level concern: I instrumented the QEMU/Nyx paths locally and directly observed a real rc_crash result being overwritten with rc_success before the host fuzzer sampled it.
Observed behavior
A target reliably triggered a real panic/crash path, but the input was sometimes reported to the fuzzer as regular instead of crash.
After adding tracing around the aux-buffer result flow, I observed this sequence:
- panic path entered
exec_result_code set to rc_crash
- reload completed
- before the next host-visible sync point,
exec_result_code was overwritten from rc_crash to rc_success
- the Python worker then sampled
success and classified the input as regular
Directly observed overwrite path
The overwrite I observed was:
- terminal result written in the panic/crash path
- then overwritten in the next normal sync path by
page_not_found handling
In other words, this was not a hypothetical overwrite candidate.
I directly observed:
crash -> success
- at the
synchronization_lock()/page_not_found path
- before host sampling
Why this happens
1. Host sampling is delayed
In kafl.fuzzer, the host does not continuously observe the aux buffer.
It only reads the result when QEMU reaches the next control-socket sync boundary.
Relevant logic is in:
kafl_fuzzer/worker/qemu.py
run_qemu()
send_payload()
So once a terminal result is written, it must remain stable until the next host-visible sync point.
2. Crash path writes the result before host sampling
In upstream kafl.qemu:
handle_hypercall_kafl_panic()
synchronization_lock_crash_found()
The panic path writes rc_crash, performs reload, and returns.
The host does not sample the aux buffer at that exact point.
3. The aux result uses a single mutable exec_result_code slot
Multiple paths write directly into the same exec_result_code byte, including:
set_crash_auxiliary_result_buffer()
set_timeout_auxiliary_result_buffer()
set_asan_auxiliary_result_buffer()
set_success_auxiliary_result_buffer()
So the crash result is not latched.
4. The next normal sync path can overwrite it
In the run I traced, the overwrite happened in:
synchronization_lock()
- specifically the
synchronization_check_page_not_found() / page_not_found handling path
That path wrote success before the host sampled the earlier crash result.
Why I think this is an upstream bug
The implementation relies on the assumption that once a terminal result is written, it will still be present when the host reads it later.
But in the current structure:
- terminal result is written first
- host sampling happens later
- normal sync code can still write into the same result slot
That makes the crash-report handoff non-atomic with respect to host observation.
Expected behavior
If the current execution has already been classified as crash, the host should not later observe that same execution as regular.
Once exec_result_code becomes terminal for an execution, later non-terminal sync logic should not downgrade it before host sampling.
Suggested fix direction
A robust fix would be:
- give terminal results (
crash, timeout, asan, etc.) a dedicated handoff path,
- record
exec_done / runtime / dirty-page metadata there,
- perform the host
PING/wait handshake immediately for that terminal result,
- only reset the result slot after host ACK,
- prevent normal sync paths from overwriting an already-terminal
exec_result_code.
Practical impact
This can cause real crashes to be misclassified as regular, depending on the post-reload sync flow.
If useful, I can also provide a minimal patch that fixes this by preserving terminal results until host handoff and guarding the normal sync overwrite path.
Thank you for reading ! :)
Hi,
I observed a real crash misclassification in
kafl.qemuthat appears to come from the upstream result handoff logic.This is not only a source-level concern: I instrumented the QEMU/Nyx paths locally and directly observed a real
rc_crashresult being overwritten withrc_successbefore the host fuzzer sampled it.Observed behavior
A target reliably triggered a real panic/crash path, but the input was sometimes reported to the fuzzer as
regularinstead ofcrash.After adding tracing around the aux-buffer result flow, I observed this sequence:
exec_result_codeset torc_crashexec_result_codewas overwritten fromrc_crashtorc_successsuccessand classified the input asregularDirectly observed overwrite path
The overwrite I observed was:
page_not_foundhandlingIn other words, this was not a hypothetical overwrite candidate.
I directly observed:
crash -> successsynchronization_lock()/page_not_foundpathWhy this happens
1. Host sampling is delayed
In
kafl.fuzzer, the host does not continuously observe the aux buffer.It only reads the result when QEMU reaches the next control-socket sync boundary.
Relevant logic is in:
kafl_fuzzer/worker/qemu.pyrun_qemu()send_payload()So once a terminal result is written, it must remain stable until the next host-visible sync point.
2. Crash path writes the result before host sampling
In upstream
kafl.qemu:handle_hypercall_kafl_panic()synchronization_lock_crash_found()The panic path writes
rc_crash, performs reload, and returns.The host does not sample the aux buffer at that exact point.
3. The aux result uses a single mutable
exec_result_codeslotMultiple paths write directly into the same
exec_result_codebyte, including:set_crash_auxiliary_result_buffer()set_timeout_auxiliary_result_buffer()set_asan_auxiliary_result_buffer()set_success_auxiliary_result_buffer()So the crash result is not latched.
4. The next normal sync path can overwrite it
In the run I traced, the overwrite happened in:
synchronization_lock()synchronization_check_page_not_found()/page_not_foundhandling pathThat path wrote
successbefore the host sampled the earlier crash result.Why I think this is an upstream bug
The implementation relies on the assumption that once a terminal result is written, it will still be present when the host reads it later.
But in the current structure:
That makes the crash-report handoff non-atomic with respect to host observation.
Expected behavior
If the current execution has already been classified as
crash, the host should not later observe that same execution asregular.Once
exec_result_codebecomes terminal for an execution, later non-terminal sync logic should not downgrade it before host sampling.Suggested fix direction
A robust fix would be:
crash,timeout,asan, etc.) a dedicated handoff path,exec_done/ runtime / dirty-page metadata there,PING/wait handshake immediately for that terminal result,exec_result_code.Practical impact
This can cause real crashes to be misclassified as
regular, depending on the post-reload sync flow.If useful, I can also provide a minimal patch that fixes this by preserving terminal results until host handoff and guarding the normal sync overwrite path.
Thank you for reading ! :)