target/i386/tcg: helper_load_seg() skips RPL check on null SS load
Host environment
-
Operating system:
Arch Linux
-
OS/kernel version:
Linux 0 7.1.8-hardened1-2-hardened #1 SMP PREEMPT_DYNAMIC Tue, 11 Aug 2026 09:16:17 +0000 x86_64 GNU/Linux
-
Architecture:
x86_64
-
QEMU flavor:
qemu-system-x86_64
-
QEMU version:
11.1.50 (v11.1.0-293-gfa19879df1)
-
QEMU command line:
qemu-system-x86_64 --no-reboot -nodefaults -device pc-testdev -device isa-debug-exit,iobase=0xf4,iosize=0x4 -display none -serial stdio -device pci-testdev -machine accel=tcg -smp 1 -kernel x86/emulator.flat
Emulated/Virtualized environment
-
Operating system:
none/kvm-unit-tests bare-metal test binary
-
OS/kernel version:
n/a
-
Architecture:
x86_64
Description of problem
The null-selector path in helper_load_seg() in target/i386/tcg/seg_helper.c never compares selector's RPL against CPL, so a null selector with non-zero RPL (such as 0x0003 ) loads successfully into SS at CPL < 3, where the SDM requires a #GP(0). As a result, two MOV to SS checks in the kvm-unit-tests emulator test fail under TCG and pass under KVM on the same host with the same QEMU binary.
Test with TCG:
PASS: mov null, %ss
FAIL: mov null, %ss (with ss.rpl != cpl)
FAIL: mov non-null, %ss (with ss.rpl != cpl)Test with KVM:
PASS: mov null, %ss
PASS: mov null, %ss (with ss.rpl != cpl)
PASS: mov non-null, %ss (with ss.rpl != cpl)Intel's Software Developer's Manual's 64-bit exception list for MOV requires that #GP(0) is raised either when a null selector is loaded into SS when CPL=3, or when a null selector is loaded into SS with CPL < 3 and CPL != RPL:
64-Bit Mode Exceptions
#GP(0) If the memory address is in a non-canonical form.
If an attempt is made to load SS register with NULL segment selector when CPL = 3.
If an attempt is made to load SS register with NULL segment selector when CPL < 3 and CPL
≠ RPLThis also causes the second failure as test_sreg() in x86/emulator64.c runs the two checks after one another, and when the non-null assertion checks read_ss() == 0 alongside the vector and error code it gets tripped by the preceding successful write into SS which should have failed. For this reason, the final condition is false even though the non-null case correctly raises a #GP with the correct error code.
I have found no prior reports of this on qemu-devel, the KVM list, or in git history.
Steps to reproduce
- Build QEMU from qemu.git master.
- Build the kvm-unit-tests from kvm-unit-tests.git master for x86_64.
- Run under TCG with the command line above, the specified two checks fail.
- Re-run with
-machine accel=kvm, both will pass.
Additional information
The same runs also surfaced #4205, where non-canonical RSP/RBP relative accesses raise #GP(0) instead of #SS(0). That appears to be an independent defect in a different code path.
Note that the test aborts shortly afterwards with an unrelated invalid handling of a jump to non-canonical address, reported in #125 , so any checks after that point in emulator.flat do not execute.
Additionally, while not a test failure the check immediately before these reports different behavior under the two accelerators. TCG prints no fep: #DB occurred after MOV SS while KVM prints no fep: #DB suppressed after MOV SS. The test accepts both outcomes, but since MOV SS is expected to defer debug exceptions until after the following instruction, this may be a second MOV to SS related divergence worth checking.