According to the pseudocode for the IRET instruction in both the Intel 64
and IA-32 Architectures Software Developer's Manual and the AMD64 Architecture
Programmer's Manual, a transition to virtual-8086 mode is allowed only if all
of the following conditions are met:
1. The new EFLAGS.VM bit is set to 1.
2. The Current Privilege Level (CPL) is 0.
3. The CPU is in protected mode (and not in long mode).
Currently, QEMU performs only the first check. This omission allows a
transition to virtual-8086 mode from long mode, and also enables the guest's
userspace to trigger this switch.
During a legitimate transition, the EFLAGS register is updated in a way that
allows modification of sensitive fields, such as IOPL and IF (which is expected,
as only privileged code should be able to initiate this transition).
However, due to the lack of appropriate checks, an unprivileged guest userspace
process can now force this transition and freely modify these fields.
This allows the userspace to:
1. Disable interrupts, preventing other processes from running on the CPU.
2. Gain direct hardware I/O access by elevating EFLAGS.IOPL to 3.
3. Crash the guest kernel by setting CS and SS to resemble segments with RPL = 0
and triggering an exception. Since the kernel is unaware that the process
entered virtual-8086 mode, it will misinterpret the exception as originating
from kernel space.
This patch fixes this bug by adding the missing CPL and long mode checks before
jumping to the `return_to_vm86` label.
Fixes: 90a9fdae1f1a ("more ring 0 operations")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3583
Signed-off-by: Andrey Polivoda <apolivodaa433@gmail.com>
Cc: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Richard Henderson <richard.henderson@linaro.org>
---
target/i386/tcg/seg_helper.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/target/i386/tcg/seg_helper.c b/target/i386/tcg/seg_helper.c
index 58aac72011..8bab2db00a 100644
--- a/target/i386/tcg/seg_helper.c
+++ b/target/i386/tcg/seg_helper.c
@@ -2068,7 +2068,8 @@ static inline void helper_ret_protected(CPUX86State *env, int shift,
new_cs = popl(&sa) & 0xffff;
if (is_iret) {
new_eflags = popl(&sa);
- if (new_eflags & VM_MASK) {
+ bool allow_vm86 = (cpl == 0) && !(env->hflags & HF_LMA_MASK);
+ if ((new_eflags & VM_MASK) && allow_vm86) {
goto return_to_vm86;
}
}
--
2.53.0