Hi Takano32, On 10/20/26 16:44, takano32 wrote:
restore_sigcontext() handed cs/ss straight to cpu_x86_load_seg(). In protected mode that ends in helper_load_seg(), which raises #GP for an invalid selector and calls cpu_loop_exit_restore() -- a longjmp out of the syscall handler. Landing in cpu_exec() that way tripsg_assert(cpu == current_cpu) in cpu_exec_longjmp_cleanup() and aborts the whole emulator. A guest that builds its own signal frame and executes rt_sigreturn (SROP) leaves cs/ss zeroed, so this is easy to hit: $ qemu-x86_64 ./hello_srop < payload # frame with cs=0, ss=0 ERROR:accel/tcg/cpu-exec.c:515:cpu_exec_longjmp_cleanup: assertion failed: (cpu == current_cpu) Aborted x86 Linux does not validate the selectors either: restore_sigcontext() only does regs->cs = sc.cs | 0x03; regs->ss = sc.ss | 0x03; and a bogus value matters only on the way back to user mode, where it costs the task and not the kernel. QEMU cannot be that casual, because the selector goes through cpu_x86_load_seg() while the syscall handler is still running. So take the existing badframe path instead: accept the selectors the CPU is already running with and return false for anything else, which makes do_rt_sigreturn() raise SIGSEGV in the guest. Tested with an x86_64 guest on an aarch64 host, against qemu master plus this patch: a frame with cs=0x33, ss=0x2b restores and exits with the requested code; a frame with cs=0, ss=0 now raises SIGSEGV in the guest instead of aborting qemu (unpatched qemu 10.2.1 aborts with the assertion above). Signed-off-by: takano32 <[email protected]>
Can you please provide me with your real name when signing patches? Anyone able to review this patch? Helge
--- linux-user/i386/signal.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/linux-user/i386/signal.c b/linux-user/i386/signal.c index 9650fac940..01d54be310 100644 --- a/linux-user/i386/signal.c +++ b/linux-user/i386/signal.c @@ -775,8 +775,32 @@ static bool restore_sigcontext(CPUX86State *env, struct target_sigcontext *sc) env->eip = tswapl(sc->rip); #endif- cpu_x86_load_seg(env, R_CS, lduw_le_p(&sc->cs) | 3);- cpu_x86_load_seg(env, R_SS, lduw_le_p(&sc->ss) | 3); + /* + * Validate the selectors before loading them. + * + * In protected mode cpu_x86_load_seg() ends up in helper_load_seg(), + * which raises #GP for a bad selector and calls cpu_loop_exit_restore(). + * That longjmps out of the syscall handler while cpu_exec_longjmp_cleanup() + * still expects the CPU it started with, so + * + * g_assert(cpu == current_cpu) + * + * fires and the whole emulator aborts. A guest that builds its own + * signal frame and executes rt_sigreturn (SROP) leaves cs/ss zeroed, + * so this is trivial to hit. Linux treats such a frame as a bad frame + * and delivers SIGSEGV, so do the same and take the badframe path. + */ + { + uint16_t cs = lduw_le_p(&sc->cs) | 3; + uint16_t ss = lduw_le_p(&sc->ss) | 3; + + if (cs != (env->segs[R_CS].selector | 3) || + ss != (env->segs[R_SS].selector | 3)) { + return false; + } + cpu_x86_load_seg(env, R_CS, cs); + cpu_x86_load_seg(env, R_SS, ss); + }tmpflags = tswapl(sc->eflags);env->eflags = (env->eflags & ~0x40DD5) | (tmpflags & 0x40DD5);
