The syscall emulation code previously wasn't interruptible via
cpu_loop_exit(), as this construct relies on a longjmp target that is not
live anymore in the syscall handling code. Consequently, longjmp() would
operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
setjmp and the necessary handling around it to make longjmp() (and by
proxy cpu_loop_exit() safe to call even within a syscall context.
Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
---
linux-user/aarch64/cpu_loop.c | 2 +-
linux-user/alpha/cpu_loop.c | 2 +-
linux-user/arm/cpu_loop.c | 2 +-
linux-user/hexagon/cpu_loop.c | 2 +-
linux-user/hppa/cpu_loop.c | 4 ++++
linux-user/i386/cpu_loop.c | 8 +++++---
linux-user/include/special-errno.h | 8 ++++++++
linux-user/loongarch64/cpu_loop.c | 5 +++--
linux-user/m68k/cpu_loop.c | 2 +-
linux-user/microblaze/cpu_loop.c | 2 +-
linux-user/mips/cpu_loop.c | 5 +++--
linux-user/or1k/cpu_loop.c | 2 +-
linux-user/ppc/cpu_loop.c | 6 ++++--
linux-user/riscv/cpu_loop.c | 2 +-
linux-user/s390x/cpu_loop.c | 2 +-
linux-user/sh4/cpu_loop.c | 2 +-
linux-user/sparc/cpu_loop.c | 4 +++-
linux-user/syscall.c | 16 ++++++++++++++++
linux-user/xtensa/cpu_loop.c | 3 +++
19 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
index 7f66a879ea..e7f643d69d 100644
--- a/linux-user/aarch64/cpu_loop.c
+++ b/linux-user/aarch64/cpu_loop.c
@@ -181,7 +181,7 @@ void cpu_loop(CPUARMState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->xregs[0] = ret;
}
break;
diff --git a/linux-user/alpha/cpu_loop.c b/linux-user/alpha/cpu_loop.c
index f93597c400..bef196b1f5 100644
--- a/linux-user/alpha/cpu_loop.c
+++ b/linux-user/alpha/cpu_loop.c
@@ -82,7 +82,7 @@ void cpu_loop(CPUAlphaState *env)
env->pc -= 4;
break;
}
- if (sysret == -QEMU_ESIGRETURN) {
+ if (sysret == -QEMU_ESIGRETURN || sysret == -QEMU_ESETPC) {
break;
}
/* Syscall writes 0 to V0 to bypass error check, similar
diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c
index 40aefc4c1d..19874f4c72 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -399,7 +399,7 @@ void cpu_loop(CPUARMState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->regs[15] -= env->thumb ? 2 : 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[0] = ret;
}
}
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index 5711055aff..9464246e9e 100644
--- a/linux-user/hexagon/cpu_loop.c
+++ b/linux-user/hexagon/cpu_loop.c
@@ -56,7 +56,7 @@ void cpu_loop(CPUHexagonState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->gpr[HEX_REG_PC] -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gpr[0] = ret;
}
break;
diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c
index 972e85c487..bd3b67059b 100644
--- a/linux-user/hppa/cpu_loop.c
+++ b/linux-user/hppa/cpu_loop.c
@@ -17,6 +17,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/compiler.h"
#include "qemu/osdep.h"
#include "qemu.h"
#include "user-internals.h"
@@ -123,7 +124,10 @@ void cpu_loop(CPUHPPAState *env)
env->iaoq_b = env->iaoq_f + 4;
break;
case -QEMU_ERESTARTSYS:
+ QEMU_FALLTHROUGH;
case -QEMU_ESIGRETURN:
+ QEMU_FALLTHROUGH;
+ case -QEMU_ESETPC:
break;
}
break;
diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c
index f3f58576af..fe922fceb5 100644
--- a/linux-user/i386/cpu_loop.c
+++ b/linux-user/i386/cpu_loop.c
@@ -181,7 +181,9 @@ static void emulate_vsyscall(CPUX86State *env)
if (ret == -TARGET_EFAULT) {
goto sigsegv;
}
- env->regs[R_EAX] = ret;
+ if (ret != -QEMU_ESETPC) {
+ env->regs[R_EAX] = ret;
+ }
/* Emulate a ret instruction to leave the vsyscall page. */
env->eip = caller;
@@ -234,7 +236,7 @@ void cpu_loop(CPUX86State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->eip -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[R_EAX] = ret;
}
break;
@@ -253,7 +255,7 @@ void cpu_loop(CPUX86State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->eip -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[R_EAX] = ret;
}
break;
diff --git a/linux-user/include/special-errno.h b/linux-user/include/special-errno.h
index 4120455baa..1db757241a 100644
--- a/linux-user/include/special-errno.h
+++ b/linux-user/include/special-errno.h
@@ -29,4 +29,12 @@
*/
#define QEMU_ESIGRETURN 513
+/*
+ * This is returned after a plugin has used the qemu_plugin_set_pc API, to
+ * indicate that the plugin deliberately changed the PC and potentially
+ * modified the register values. The main loop should not touch the guest
+ * registers for this reason.
+ */
+#define QEMU_ESETPC 514
+
#endif /* SPECIAL_ERRNO_H */
diff --git a/linux-user/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c
index 26a5ce3a93..603fcc39c7 100644
--- a/linux-user/loongarch64/cpu_loop.c
+++ b/linux-user/loongarch64/cpu_loop.c
@@ -44,9 +44,10 @@ void cpu_loop(CPULoongArchState *env)
env->pc -= 4;
break;
}
- if (ret == -QEMU_ESIGRETURN) {
+ if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) {
/*
- * Returning from a successful sigreturn syscall.
+ * Returning from a successful sigreturn syscall or from
+ * control flow diversion in a plugin callback.
* Avoid clobbering register state.
*/
break;
diff --git a/linux-user/m68k/cpu_loop.c b/linux-user/m68k/cpu_loop.c
index 2c9f628241..b98ca8ff7b 100644
--- a/linux-user/m68k/cpu_loop.c
+++ b/linux-user/m68k/cpu_loop.c
@@ -66,7 +66,7 @@ void cpu_loop(CPUM68KState *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->dregs[0] = ret;
}
}
diff --git a/linux-user/microblaze/cpu_loop.c b/linux-user/microblaze/cpu_loop.c
index 78506ab23d..06d92c0b90 100644
--- a/linux-user/microblaze/cpu_loop.c
+++ b/linux-user/microblaze/cpu_loop.c
@@ -54,7 +54,7 @@ void cpu_loop(CPUMBState *env)
if (ret == -QEMU_ERESTARTSYS) {
/* Wind back to before the syscall. */
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[3] = ret;
}
/* All syscall exits result in guest r14 being equal to the
diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c
index 2365de1de1..af98138eb2 100644
--- a/linux-user/mips/cpu_loop.c
+++ b/linux-user/mips/cpu_loop.c
@@ -140,8 +140,9 @@ done_syscall:
env->active_tc.PC -= 4;
break;
}
- if (ret == -QEMU_ESIGRETURN) {
- /* Returning from a successful sigreturn syscall.
+ if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) {
+ /* Returning from a successful sigreturn syscall or from
+ control flow diversion in a plugin callback.
Avoid clobbering register state. */
break;
}
diff --git a/linux-user/or1k/cpu_loop.c b/linux-user/or1k/cpu_loop.c
index 2167d880d5..e7e9929e6f 100644
--- a/linux-user/or1k/cpu_loop.c
+++ b/linux-user/or1k/cpu_loop.c
@@ -48,7 +48,7 @@ void cpu_loop(CPUOpenRISCState *env)
cpu_get_gpr(env, 8), 0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
cpu_set_gpr(env, 11, ret);
}
break;
diff --git a/linux-user/ppc/cpu_loop.c b/linux-user/ppc/cpu_loop.c
index b0b0cb14b4..1f8aae14bb 100644
--- a/linux-user/ppc/cpu_loop.c
+++ b/linux-user/ppc/cpu_loop.c
@@ -340,8 +340,10 @@ void cpu_loop(CPUPPCState *env)
env->nip -= 4;
break;
}
- if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
- /* Returning from a successful sigreturn syscall.
+ if (ret == (target_ulong)(-QEMU_ESIGRETURN)
+ || ret == (target_ulong)(-QEMU_ESETPC)) {
+ /* Returning from a successful sigreturn syscall or from
+ control flow diversion in a plugin callback.
Avoid corrupting register state. */
break;
}
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index ce542540c2..eecc8d1517 100644
--- a/linux-user/riscv/cpu_loop.c
+++ b/linux-user/riscv/cpu_loop.c
@@ -65,7 +65,7 @@ void cpu_loop(CPURISCVState *env)
}
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 4;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gpr[xA0] = ret;
}
if (cs->singlestep_enabled) {
diff --git a/linux-user/s390x/cpu_loop.c b/linux-user/s390x/cpu_loop.c
index 4929b32e1f..67d2a803fb 100644
--- a/linux-user/s390x/cpu_loop.c
+++ b/linux-user/s390x/cpu_loop.c
@@ -83,7 +83,7 @@ void cpu_loop(CPUS390XState *env)
env->regs[6], env->regs[7], 0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->psw.addr -= env->int_svc_ilen;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->regs[2] = ret;
}
diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c
index 0c9d7e9c46..ee2958d0d9 100644
--- a/linux-user/sh4/cpu_loop.c
+++ b/linux-user/sh4/cpu_loop.c
@@ -50,7 +50,7 @@ void cpu_loop(CPUSH4State *env)
0, 0);
if (ret == -QEMU_ERESTARTSYS) {
env->pc -= 2;
- } else if (ret != -QEMU_ESIGRETURN) {
+ } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) {
env->gregs[0] = ret;
}
break;
diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c
index 7391e2add8..f054316dce 100644
--- a/linux-user/sparc/cpu_loop.c
+++ b/linux-user/sparc/cpu_loop.c
@@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
env->regwptr[2], env->regwptr[3],
env->regwptr[4], env->regwptr[5],
0, 0);
- if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
+ if (ret == -QEMU_ERESTARTSYS
+ || ret == -QEMU_ESIGRETURN
+ || ret == -QEMU_ESETPC) {
break;
}
if ((abi_ulong)ret >= (abi_ulong)(-515)) {
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index d466d0e32f..99e1ed97d9 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -43,6 +43,7 @@
#include <linux/capability.h>
#include <sched.h>
#include <sys/timex.h>
+#include <setjmp.h>
#include <sys/socket.h>
#include <linux/sockios.h>
#include <sys/un.h>
@@ -600,6 +601,9 @@ const char *target_strerror(int err)
if (err == QEMU_ESIGRETURN) {
return "Successful exit from sigreturn";
}
+ if (err == QEMU_ESETPC) {
+ return "Successfully redirected control flow";
+ }
return strerror(target_to_host_errno(err));
}
@@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
return -QEMU_ESIGRETURN;
}
+ /*
+ * Set up a longjmp target here so that we can call cpu_loop_exit to
+ * redirect control flow back to the main loop even from within
+ * syscall-related plugin callbacks.
+ * For other types of callbacks or longjmp call sites, the longjmp target
+ * is set up in the cpu loop itself but in syscalls the target is not live
+ * anymore.
+ */
+ if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
+ return -QEMU_ESETPC;
+ }
+
record_syscall_start(cpu, num, arg1,
arg2, arg3, arg4, arg5, arg6, arg7, arg8);
diff --git a/linux-user/xtensa/cpu_loop.c b/linux-user/xtensa/cpu_loop.c
index a0ff10eff8..7680e243bb 100644
--- a/linux-user/xtensa/cpu_loop.c
+++ b/linux-user/xtensa/cpu_loop.c
@@ -17,6 +17,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/compiler.h"
#include "qemu/osdep.h"
#include "qemu.h"
#include "user-internals.h"
@@ -185,6 +186,8 @@ void cpu_loop(CPUXtensaState *env)
env->pc -= 3;
break;
+ case -QEMU_ESETPC:
+ QEMU_FALLTHROUGH;
case -QEMU_ESIGRETURN:
break;
}
--
2.53.0
On 2/24/26 7:50 AM, Florian Hofhammer wrote:
> The syscall emulation code previously wasn't interruptible via
> cpu_loop_exit(), as this construct relies on a longjmp target that is not
> live anymore in the syscall handling code. Consequently, longjmp() would
> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
> setjmp and the necessary handling around it to make longjmp() (and by
> proxy cpu_loop_exit() safe to call even within a syscall context.
>
> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
> ---
> linux-user/aarch64/cpu_loop.c | 2 +-
> linux-user/alpha/cpu_loop.c | 2 +-
> linux-user/arm/cpu_loop.c | 2 +-
> linux-user/hexagon/cpu_loop.c | 2 +-
> linux-user/hppa/cpu_loop.c | 4 ++++
> linux-user/i386/cpu_loop.c | 8 +++++---
> linux-user/include/special-errno.h | 8 ++++++++
> linux-user/loongarch64/cpu_loop.c | 5 +++--
> linux-user/m68k/cpu_loop.c | 2 +-
> linux-user/microblaze/cpu_loop.c | 2 +-
> linux-user/mips/cpu_loop.c | 5 +++--
> linux-user/or1k/cpu_loop.c | 2 +-
> linux-user/ppc/cpu_loop.c | 6 ++++--
> linux-user/riscv/cpu_loop.c | 2 +-
> linux-user/s390x/cpu_loop.c | 2 +-
> linux-user/sh4/cpu_loop.c | 2 +-
> linux-user/sparc/cpu_loop.c | 4 +++-
> linux-user/syscall.c | 16 ++++++++++++++++
> linux-user/xtensa/cpu_loop.c | 3 +++
> 19 files changed, 59 insertions(+), 20 deletions(-)
>
> diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c
> index 7391e2add8..f054316dce 100644
> --- a/linux-user/sparc/cpu_loop.c
> +++ b/linux-user/sparc/cpu_loop.c
> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
> env->regwptr[2], env->regwptr[3],
> env->regwptr[4], env->regwptr[5],
> 0, 0);
> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
> + if (ret == -QEMU_ERESTARTSYS
> + || ret == -QEMU_ESIGRETURN
> + || ret == -QEMU_ESETPC) {
> break;
> }
Just a style nit:
if (ret == -QEMU_ERESTARTSYS ||
ret == -QEMU_ESIGRETURN ||
ret == -QEMU_ESETPC) {
> if ((abi_ulong)ret >= (abi_ulong)(-515)) {
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index d466d0e32f..99e1ed97d9 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -43,6 +43,7 @@
> #include <linux/capability.h>
> #include <sched.h>
> #include <sys/timex.h>
> +#include <setjmp.h>
> #include <sys/socket.h>
> #include <linux/sockios.h>
> #include <sys/un.h>
> @@ -600,6 +601,9 @@ const char *target_strerror(int err)
> if (err == QEMU_ESIGRETURN) {
> return "Successful exit from sigreturn";
> }
> + if (err == QEMU_ESETPC) {
> + return "Successfully redirected control flow";
> + }
>
> return strerror(target_to_host_errno(err));
> }
> @@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
> return -QEMU_ESIGRETURN;
> }
>
> + /*
> + * Set up a longjmp target here so that we can call cpu_loop_exit to
> + * redirect control flow back to the main loop even from within
> + * syscall-related plugin callbacks.
> + * For other types of callbacks or longjmp call sites, the longjmp target
> + * is set up in the cpu loop itself but in syscalls the target is not live
> + * anymore.
> + */
> + if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
> + return -QEMU_ESETPC;
> + }
> +
Makes sense, and I guess you found that when running the series test.
Do you need all the new QEMU_FALLTHROUGH introduced here? Is it for
removing warnings?
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Pierrick Bouvier <pierrick.bouvier@linaro.org> writes:
> On 2/24/26 7:50 AM, Florian Hofhammer wrote:
>> The syscall emulation code previously wasn't interruptible via
>> cpu_loop_exit(), as this construct relies on a longjmp target that is not
>> live anymore in the syscall handling code. Consequently, longjmp() would
>> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
>> setjmp and the necessary handling around it to make longjmp() (and by
>> proxy cpu_loop_exit() safe to call even within a syscall context.
>> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
>> ---
>> linux-user/aarch64/cpu_loop.c | 2 +-
>> linux-user/alpha/cpu_loop.c | 2 +-
>> linux-user/arm/cpu_loop.c | 2 +-
>> linux-user/hexagon/cpu_loop.c | 2 +-
>> linux-user/hppa/cpu_loop.c | 4 ++++
>> linux-user/i386/cpu_loop.c | 8 +++++---
>> linux-user/include/special-errno.h | 8 ++++++++
>> linux-user/loongarch64/cpu_loop.c | 5 +++--
>> linux-user/m68k/cpu_loop.c | 2 +-
>> linux-user/microblaze/cpu_loop.c | 2 +-
>> linux-user/mips/cpu_loop.c | 5 +++--
>> linux-user/or1k/cpu_loop.c | 2 +-
>> linux-user/ppc/cpu_loop.c | 6 ++++--
>> linux-user/riscv/cpu_loop.c | 2 +-
>> linux-user/s390x/cpu_loop.c | 2 +-
>> linux-user/sh4/cpu_loop.c | 2 +-
>> linux-user/sparc/cpu_loop.c | 4 +++-
>> linux-user/syscall.c | 16 ++++++++++++++++
>> linux-user/xtensa/cpu_loop.c | 3 +++
>> 19 files changed, 59 insertions(+), 20 deletions(-)
>> diff --git a/linux-user/sparc/cpu_loop.c
>> b/linux-user/sparc/cpu_loop.c
>> index 7391e2add8..f054316dce 100644
>> --- a/linux-user/sparc/cpu_loop.c
>> +++ b/linux-user/sparc/cpu_loop.c
>> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
>> env->regwptr[2], env->regwptr[3],
>> env->regwptr[4], env->regwptr[5],
>> 0, 0);
>> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
>> + if (ret == -QEMU_ERESTARTSYS
>> + || ret == -QEMU_ESIGRETURN
>> + || ret == -QEMU_ESETPC) {
>> break;
>> }
>
> Just a style nit:
> if (ret == -QEMU_ERESTARTSYS ||
> ret == -QEMU_ESIGRETURN ||
> ret == -QEMU_ESETPC) {
I was hopping the ret test could be wrapped up into a helper but it
seems sparc and x86 have enough variation in handled ret codes to make
that difficult.
<snip>
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
On 25/02/2026 10:25, Alex Bennée wrote:
> Pierrick Bouvier <pierrick.bouvier@linaro.org> writes:
>
>> On 2/24/26 7:50 AM, Florian Hofhammer wrote:
>>> The syscall emulation code previously wasn't interruptible via
>>> cpu_loop_exit(), as this construct relies on a longjmp target that is not
>>> live anymore in the syscall handling code. Consequently, longjmp() would
>>> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
>>> setjmp and the necessary handling around it to make longjmp() (and by
>>> proxy cpu_loop_exit() safe to call even within a syscall context.
>>> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
>>> ---
>>> linux-user/aarch64/cpu_loop.c | 2 +-
>>> linux-user/alpha/cpu_loop.c | 2 +-
>>> linux-user/arm/cpu_loop.c | 2 +-
>>> linux-user/hexagon/cpu_loop.c | 2 +-
>>> linux-user/hppa/cpu_loop.c | 4 ++++
>>> linux-user/i386/cpu_loop.c | 8 +++++---
>>> linux-user/include/special-errno.h | 8 ++++++++
>>> linux-user/loongarch64/cpu_loop.c | 5 +++--
>>> linux-user/m68k/cpu_loop.c | 2 +-
>>> linux-user/microblaze/cpu_loop.c | 2 +-
>>> linux-user/mips/cpu_loop.c | 5 +++--
>>> linux-user/or1k/cpu_loop.c | 2 +-
>>> linux-user/ppc/cpu_loop.c | 6 ++++--
>>> linux-user/riscv/cpu_loop.c | 2 +-
>>> linux-user/s390x/cpu_loop.c | 2 +-
>>> linux-user/sh4/cpu_loop.c | 2 +-
>>> linux-user/sparc/cpu_loop.c | 4 +++-
>>> linux-user/syscall.c | 16 ++++++++++++++++
>>> linux-user/xtensa/cpu_loop.c | 3 +++
>>> 19 files changed, 59 insertions(+), 20 deletions(-)
>>> diff --git a/linux-user/sparc/cpu_loop.c
>>> b/linux-user/sparc/cpu_loop.c
>>> index 7391e2add8..f054316dce 100644
>>> --- a/linux-user/sparc/cpu_loop.c
>>> +++ b/linux-user/sparc/cpu_loop.c
>>> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
>>> env->regwptr[2], env->regwptr[3],
>>> env->regwptr[4], env->regwptr[5],
>>> 0, 0);
>>> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
>>> + if (ret == -QEMU_ERESTARTSYS
>>> + || ret == -QEMU_ESIGRETURN
>>> + || ret == -QEMU_ESETPC) {
>>> break;
>>> }
>>
>> Just a style nit:
>> if (ret == -QEMU_ERESTARTSYS ||
>> ret == -QEMU_ESIGRETURN ||
>> ret == -QEMU_ESETPC) {
>
> I was hopping the ret test could be wrapped up into a helper but it
> seems sparc and x86 have enough variation in handled ret codes to make
> that difficult.
It's not just x86 and sparc, there generally seems to not be consensus
on whether return codes are checked via a single conditional check (as
is the case with sparc), dedicated conditional checks for each possible
error value, or via switch statements. I think moving that into a helper
would require a bit of refactoring across architectures.
I could check that out for a follow-up patch set if you think this makes
sense.
Best regards,
Florian
Florian Hofhammer <florian.hofhammer@epfl.ch> writes:
> On 25/02/2026 10:25, Alex Bennée wrote:
>> Pierrick Bouvier <pierrick.bouvier@linaro.org> writes:
>>
>>> On 2/24/26 7:50 AM, Florian Hofhammer wrote:
>>>> The syscall emulation code previously wasn't interruptible via
>>>> cpu_loop_exit(), as this construct relies on a longjmp target that is not
>>>> live anymore in the syscall handling code. Consequently, longjmp() would
>>>> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
>>>> setjmp and the necessary handling around it to make longjmp() (and by
>>>> proxy cpu_loop_exit() safe to call even within a syscall context.
>>>> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
>>>> ---
>>>> linux-user/aarch64/cpu_loop.c | 2 +-
>>>> linux-user/alpha/cpu_loop.c | 2 +-
>>>> linux-user/arm/cpu_loop.c | 2 +-
>>>> linux-user/hexagon/cpu_loop.c | 2 +-
>>>> linux-user/hppa/cpu_loop.c | 4 ++++
>>>> linux-user/i386/cpu_loop.c | 8 +++++---
>>>> linux-user/include/special-errno.h | 8 ++++++++
>>>> linux-user/loongarch64/cpu_loop.c | 5 +++--
>>>> linux-user/m68k/cpu_loop.c | 2 +-
>>>> linux-user/microblaze/cpu_loop.c | 2 +-
>>>> linux-user/mips/cpu_loop.c | 5 +++--
>>>> linux-user/or1k/cpu_loop.c | 2 +-
>>>> linux-user/ppc/cpu_loop.c | 6 ++++--
>>>> linux-user/riscv/cpu_loop.c | 2 +-
>>>> linux-user/s390x/cpu_loop.c | 2 +-
>>>> linux-user/sh4/cpu_loop.c | 2 +-
>>>> linux-user/sparc/cpu_loop.c | 4 +++-
>>>> linux-user/syscall.c | 16 ++++++++++++++++
>>>> linux-user/xtensa/cpu_loop.c | 3 +++
>>>> 19 files changed, 59 insertions(+), 20 deletions(-)
>>>> diff --git a/linux-user/sparc/cpu_loop.c
>>>> b/linux-user/sparc/cpu_loop.c
>>>> index 7391e2add8..f054316dce 100644
>>>> --- a/linux-user/sparc/cpu_loop.c
>>>> +++ b/linux-user/sparc/cpu_loop.c
>>>> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
>>>> env->regwptr[2], env->regwptr[3],
>>>> env->regwptr[4], env->regwptr[5],
>>>> 0, 0);
>>>> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
>>>> + if (ret == -QEMU_ERESTARTSYS
>>>> + || ret == -QEMU_ESIGRETURN
>>>> + || ret == -QEMU_ESETPC) {
>>>> break;
>>>> }
>>>
>>> Just a style nit:
>>> if (ret == -QEMU_ERESTARTSYS ||
>>> ret == -QEMU_ESIGRETURN ||
>>> ret == -QEMU_ESETPC) {
>>
>> I was hopping the ret test could be wrapped up into a helper but it
>> seems sparc and x86 have enough variation in handled ret codes to make
>> that difficult.
>
> It's not just x86 and sparc, there generally seems to not be consensus
> on whether return codes are checked via a single conditional check (as
> is the case with sparc), dedicated conditional checks for each possible
> error value, or via switch statements. I think moving that into a helper
> would require a bit of refactoring across architectures.
> I could check that out for a follow-up patch set if you think this makes
> sense.
I think we can live with it for now:
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
>
> Best regards,
> Florian
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
On 24/02/2026 22:05, Pierrick Bouvier wrote:
> On 2/24/26 7:50 AM, Florian Hofhammer wrote:
>> The syscall emulation code previously wasn't interruptible via
>> cpu_loop_exit(), as this construct relies on a longjmp target that is not
>> live anymore in the syscall handling code. Consequently, longjmp() would
>> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
>> setjmp and the necessary handling around it to make longjmp() (and by
>> proxy cpu_loop_exit() safe to call even within a syscall context.
>>
>> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
>> ---
>> linux-user/aarch64/cpu_loop.c | 2 +-
>> linux-user/alpha/cpu_loop.c | 2 +-
>> linux-user/arm/cpu_loop.c | 2 +-
>> linux-user/hexagon/cpu_loop.c | 2 +-
>> linux-user/hppa/cpu_loop.c | 4 ++++
>> linux-user/i386/cpu_loop.c | 8 +++++---
>> linux-user/include/special-errno.h | 8 ++++++++
>> linux-user/loongarch64/cpu_loop.c | 5 +++--
>> linux-user/m68k/cpu_loop.c | 2 +-
>> linux-user/microblaze/cpu_loop.c | 2 +-
>> linux-user/mips/cpu_loop.c | 5 +++--
>> linux-user/or1k/cpu_loop.c | 2 +-
>> linux-user/ppc/cpu_loop.c | 6 ++++--
>> linux-user/riscv/cpu_loop.c | 2 +-
>> linux-user/s390x/cpu_loop.c | 2 +-
>> linux-user/sh4/cpu_loop.c | 2 +-
>> linux-user/sparc/cpu_loop.c | 4 +++-
>> linux-user/syscall.c | 16 ++++++++++++++++
>> linux-user/xtensa/cpu_loop.c | 3 +++
>> 19 files changed, 59 insertions(+), 20 deletions(-)
>>
>> diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c
>> index 7391e2add8..f054316dce 100644
>> --- a/linux-user/sparc/cpu_loop.c
>> +++ b/linux-user/sparc/cpu_loop.c
>> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
>> env->regwptr[2], env->regwptr[3],
>> env->regwptr[4], env->regwptr[5],
>> 0, 0);
>> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
>> + if (ret == -QEMU_ERESTARTSYS
>> + || ret == -QEMU_ESIGRETURN
>> + || ret == -QEMU_ESETPC) {
>> break;
>> }
>
> Just a style nit:
> if (ret == -QEMU_ERESTARTSYS ||
> ret == -QEMU_ESIGRETURN ||
> ret == -QEMU_ESETPC) {
>
>> if ((abi_ulong)ret >= (abi_ulong)(-515)) {
>> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
>> index d466d0e32f..99e1ed97d9 100644
>> --- a/linux-user/syscall.c
>> +++ b/linux-user/syscall.c
>> @@ -43,6 +43,7 @@
>> #include <linux/capability.h>
>> #include <sched.h>
>> #include <sys/timex.h>
>> +#include <setjmp.h>
>> #include <sys/socket.h>
>> #include <linux/sockios.h>
>> #include <sys/un.h>
>> @@ -600,6 +601,9 @@ const char *target_strerror(int err)
>> if (err == QEMU_ESIGRETURN) {
>> return "Successful exit from sigreturn";
>> }
>> + if (err == QEMU_ESETPC) {
>> + return "Successfully redirected control flow";
>> + }
>> return strerror(target_to_host_errno(err));
>> }
>> @@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
>> return -QEMU_ESIGRETURN;
>> }
>> + /*
>> + * Set up a longjmp target here so that we can call cpu_loop_exit to
>> + * redirect control flow back to the main loop even from within
>> + * syscall-related plugin callbacks.
>> + * For other types of callbacks or longjmp call sites, the longjmp target
>> + * is set up in the cpu loop itself but in syscalls the target is not live
>> + * anymore.
>> + */
>> + if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
>> + return -QEMU_ESETPC;
>> + }
>> +
>
> Makes sense, and I guess you found that when running the series test.
>
> Do you need all the new QEMU_FALLTHROUGH introduced here? Is it for removing warnings?
I think they're not strictly necessary, but if I recall correctly, I had
warnings otherwise.
I can remove them if you prefer but I think making the fallthrough
explicit encodes the intent better.
>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
On 2/25/26 12:02 AM, Florian Hofhammer wrote:
> On 24/02/2026 22:05, Pierrick Bouvier wrote:
>> On 2/24/26 7:50 AM, Florian Hofhammer wrote:
>>> The syscall emulation code previously wasn't interruptible via
>>> cpu_loop_exit(), as this construct relies on a longjmp target that is not
>>> live anymore in the syscall handling code. Consequently, longjmp() would
>>> operate on a (potentially overwritten) stale jump buffer. This patch adds an additional
>>> setjmp and the necessary handling around it to make longjmp() (and by
>>> proxy cpu_loop_exit() safe to call even within a syscall context.
>>>
>>> Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
>>> ---
>>> linux-user/aarch64/cpu_loop.c | 2 +-
>>> linux-user/alpha/cpu_loop.c | 2 +-
>>> linux-user/arm/cpu_loop.c | 2 +-
>>> linux-user/hexagon/cpu_loop.c | 2 +-
>>> linux-user/hppa/cpu_loop.c | 4 ++++
>>> linux-user/i386/cpu_loop.c | 8 +++++---
>>> linux-user/include/special-errno.h | 8 ++++++++
>>> linux-user/loongarch64/cpu_loop.c | 5 +++--
>>> linux-user/m68k/cpu_loop.c | 2 +-
>>> linux-user/microblaze/cpu_loop.c | 2 +-
>>> linux-user/mips/cpu_loop.c | 5 +++--
>>> linux-user/or1k/cpu_loop.c | 2 +-
>>> linux-user/ppc/cpu_loop.c | 6 ++++--
>>> linux-user/riscv/cpu_loop.c | 2 +-
>>> linux-user/s390x/cpu_loop.c | 2 +-
>>> linux-user/sh4/cpu_loop.c | 2 +-
>>> linux-user/sparc/cpu_loop.c | 4 +++-
>>> linux-user/syscall.c | 16 ++++++++++++++++
>>> linux-user/xtensa/cpu_loop.c | 3 +++
>>> 19 files changed, 59 insertions(+), 20 deletions(-)
>>>
>>> diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c
>>> index 7391e2add8..f054316dce 100644
>>> --- a/linux-user/sparc/cpu_loop.c
>>> +++ b/linux-user/sparc/cpu_loop.c
>>> @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env)
>>> env->regwptr[2], env->regwptr[3],
>>> env->regwptr[4], env->regwptr[5],
>>> 0, 0);
>>> - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) {
>>> + if (ret == -QEMU_ERESTARTSYS
>>> + || ret == -QEMU_ESIGRETURN
>>> + || ret == -QEMU_ESETPC) {
>>> break;
>>> }
>>
>> Just a style nit:
>> if (ret == -QEMU_ERESTARTSYS ||
>> ret == -QEMU_ESIGRETURN ||
>> ret == -QEMU_ESETPC) {
>>
>>> if ((abi_ulong)ret >= (abi_ulong)(-515)) {
>>> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
>>> index d466d0e32f..99e1ed97d9 100644
>>> --- a/linux-user/syscall.c
>>> +++ b/linux-user/syscall.c
>>> @@ -43,6 +43,7 @@
>>> #include <linux/capability.h>
>>> #include <sched.h>
>>> #include <sys/timex.h>
>>> +#include <setjmp.h>
>>> #include <sys/socket.h>
>>> #include <linux/sockios.h>
>>> #include <sys/un.h>
>>> @@ -600,6 +601,9 @@ const char *target_strerror(int err)
>>> if (err == QEMU_ESIGRETURN) {
>>> return "Successful exit from sigreturn";
>>> }
>>> + if (err == QEMU_ESETPC) {
>>> + return "Successfully redirected control flow";
>>> + }
>>> return strerror(target_to_host_errno(err));
>>> }
>>> @@ -14410,6 +14414,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
>>> return -QEMU_ESIGRETURN;
>>> }
>>> + /*
>>> + * Set up a longjmp target here so that we can call cpu_loop_exit to
>>> + * redirect control flow back to the main loop even from within
>>> + * syscall-related plugin callbacks.
>>> + * For other types of callbacks or longjmp call sites, the longjmp target
>>> + * is set up in the cpu loop itself but in syscalls the target is not live
>>> + * anymore.
>>> + */
>>> + if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) {
>>> + return -QEMU_ESETPC;
>>> + }
>>> +
>>
>> Makes sense, and I guess you found that when running the series test.
>>
>> Do you need all the new QEMU_FALLTHROUGH introduced here? Is it for removing warnings?
>
> I think they're not strictly necessary, but if I recall correctly, I had
> warnings otherwise.
> I can remove them if you prefer but I think making the fallthrough
> explicit encodes the intent better.
>
If that compiles warning-free without it, you can remove it. Else, leave
it as it is.
Fallthrough attribute is precisely used to silence warning compiler, and
not to document code, even though it can help with that.
Thanks,
Pierrick
© 2016 - 2026 Red Hat, Inc.