On Mon, Mar 2, 2026 at 7:03 AM Florian Hofhammer <[email protected]>
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 <[email protected]>
> ---
>

So bsd-user would need something similar, I'd think. But it still uses the
older-style
TARGET_EJUSTRETURN. With half of the archs still out of tree, I'm not
entirely
sure the best way to approach this.

The code does what you say, which is good, but there may be some subtle
reason to do something else.

Reviewed-by: Warner Losh <[email protected]>


>  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         |  1 +
>  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         |  9 ++++++---
>  linux-user/or1k/cpu_loop.c         |  2 +
>  linux-user/ppc/cpu_loop.c          | 10 +++++++---
>  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       |  1 +
>  19 files changed, 60 insertions(+), 22 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..4b4b663052 100644
> --- a/linux-user/hppa/cpu_loop.c
> +++ b/linux-user/hppa/cpu_loop.c
> @@ -124,6 +124,7 @@ void cpu_loop(CPUHPPAState *env)
>                  break;
>              case -QEMU_ERESTARTSYS:
>              case -QEMU_ESIGRETURN:
> +            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..fa264b27ec 100644
> --- a/linux-user/mips/cpu_loop.c
> +++ b/linux-user/mips/cpu_loop.c
> @@ -140,9 +140,12 @@ done_syscall:
>                  env->active_tc.PC -= 4;
>                  break;
>              }
> -            if (ret == -QEMU_ESIGRETURN) {
> -                /* Returning from a successful sigreturn syscall.
> -                   Avoid clobbering register state.  */
> +            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;
>              }
>              if ((abi_ulong)ret >= (abi_ulong)-1133) {
> 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..1f9ee20bd0 100644
> --- a/linux-user/ppc/cpu_loop.c
> +++ b/linux-user/ppc/cpu_loop.c
> @@ -340,9 +340,13 @@ void cpu_loop(CPUPPCState *env)
>                  env->nip -= 4;
>                  break;
>              }
> -            if (ret == (target_ulong)(-QEMU_ESIGRETURN)) {
> -                /* Returning from a successful sigreturn syscall.
> -                   Avoid corrupting register state.  */
> +            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;
>              }
>              if (ret > (target_ulong)(-515)) {
> 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..ab633eeae3 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..d2b4ccdfad 100644
> --- a/linux-user/xtensa/cpu_loop.c
> +++ b/linux-user/xtensa/cpu_loop.c
> @@ -186,6 +186,7 @@ void cpu_loop(CPUXtensaState *env)
>                      break;
>
>                  case -QEMU_ESIGRETURN:
> +                case -QEMU_ESETPC:
>                      break;
>                  }
>                  break;
> --
> 2.53.0
>

Reply via email to