Use-after-free after flush in TCG accelerator
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
QEMU |
Fix Released
|
Undecided
|
Alex Bennée |
Bug Description
I believe I found a UAF in TCG that can lead to a guest VM escape. The security list informed me "This can not be treated as a security issue." and to post it here. I am looking at the 4.2.0 source code. The issue requires a race and I will try to describe it in terms of three concurrent threads.
Thread A:
A1. qemu_tcg_
A2. qemu_wait_io_event => qemu_wait_
A3. start_exclusive critical section entered
A4. do_tb_flush is called, TB memory freed/re-allocated
A5. end_exclusive exits critical section
Thread B:
B1. qemu_tcg_
B2. tcg_cpu_exec => cpu_exec => tb_find => tb_gen_code
B3. tcg_tb_alloc obtains a new TB
Thread C:
C1. qemu_tcg_
C2. cpu_exec_
C3. TB obtained with tb_lookup_
C4. start_exclusive critical section entered
C5. cpu_tb_exec executes the TB code
C6. end_exclusive exits critical section
Consider the following sequence of events:
B2 => B3 => C3 (same TB as B2) => A3 => A4 (TB freed) => A5 => B2 =>
B3 (re-allocates TB from B2) => C4 => C5 (freed/reused TB now executing) => C6
In short, because thread C uses the TB in the critical section, there is no guarantee that the pointer has not been "freed" (rather the memory is marked as re-usable) and therefore a use-after-free occurs.
Since the TCG generated code can be in the same memory as the TB data structure, it is possible for an attacker to overwrite the UAF pointer with code generated from TCG. This can overwrite key pointer values and could lead to code execution on the host outside of the TCG sandbox.
CVE References
description: | updated |
tags: | added: tcg |
Changed in qemu: | |
assignee: | nobody → Alex Bennée (ajbennee) |
status: | New → Confirmed |
Changed in qemu: | |
status: | Fix Committed → Fix Released |
The bug describes a race whereby cpu_exec_ step_atomic can acquire a TB
which is invalidated by a tb_flush before we execute it. This doesn't
affect the other cpu_exec modes as a tb_flush by it's nature can only
occur on a quiescent system. The race was described as:
B2. tcg_cpu_exec => cpu_exec => tb_find => tb_gen_code
B3. tcg_tb_alloc obtains a new TB
C3. TB obtained with tb_lookup_ _cpu_state or tb_gen_code
(same TB as B2)
A3. start_exclusive critical section entered
A4. do_tb_flush is called, TB memory freed/re-allocated
A5. end_exclusive exits critical section
B2. tcg_cpu_exec => cpu_exec => tb_find => tb_gen_code
B3. tcg_tb_alloc reallocates TB from B2
C4. start_exclusive critical section entered
C5. cpu_tb_exec executes the TB code that was free in A4
The simplest fix is to widen the exclusive period to include the TB
lookup. As a result we can drop the complication of checking we are in
the exclusive region before we end it.
Signed-off-by: Alex Bennée <email address hidden> tcg/cpu- exec.c | 21 +++++++ ++++--- -------
Cc: Yifan <email address hidden>
Cc: Bug 1863025 <email address hidden>
---
accel/
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/accel/ tcg/cpu- exec.c b/accel/ tcg/cpu- exec.c .d95c4848a47 100644 tcg/cpu- exec.c tcg/cpu- exec.c step_atomic( CPUState *cpu)
index 2560c90eec7.
--- a/accel/
+++ b/accel/
@@ -240,6 +240,8 @@ void cpu_exec_
uint32_t cf_mask = cflags & CF_HASH_MASK;
if (sigsetjmp( cpu->jmp_ env, 0) == 0) { _cpu_state( cpu, &pc, &cs_base, &flags, cf_mask);
mmap_ lock(); step_atomic( CPUState *cpu)
mmap_ unlock( );
+ start_exclusive();
+
tb = tb_lookup_
if (tb == NULL) {
@@ -247,8 +249,6 @@ void cpu_exec_
}
- start_exclusive();
parallel_ cpus = false;
cc->cpu_ exec_enter( cpu); step_atomic( CPUState *cpu)
qemu_ plugin_ disable_ mem_helpers( cpu);
-
/* Since we got here, we know that parallel_cpus must be true. */
@@ -271,14 +271,15 @@ void cpu_exec_
}
- if (cpu_in_ exclusive_ context( cpu)) { cpu_in_ exclusive_ context( cpu));
- /* We might longjump out of either the codegen or the
- * execution, so must make sure we only end the exclusive
- * region if we started it.
- */
- parallel_cpus = true;
- end_exclusive();
- }
+
+ /*
+ * As we start the exclusive region before codegen we must still
+ * be in the region if we longjump out of either the codegen or
+ * the execution.
+ */
+ g_assert(
+ parallel_cpus = true;
+ end_exclusive();
}
struct tb_desc {
--
2.20.1