# HG changeset patch # User Taylor R Campbell # Date 1783575156 0 # Thu Jul 09 05:32:36 2026 +0000 # Branch trunk # Node ID e5e730145a70a268164650cdc4466b721fccfd84 # Parent 15c667d8eac5446d5afa7c99df1b13a6ad7d7a60 # EXP-Topic riastradh-pr60426-x86xsavesignal WIP: x86: Save and restore all extended CPU state on signals. WARNING: COMPILE-TESTED ONLY Previously we would only save and restore what FXSAVE does, which is the x87 and SSE registers. To save and restore the upper halves of the YMM or ZMM registers (AVX/AVX2/AVX512), or the AVX512 registers ZMM16..ZMM31, or in the future the enormous AMX state, we need to do more. When delivering a signal to a thread that has actually made use of these extended registers, instead of storing FXSAVE in the mcontext_t with _UC_FPU set, we: 1. allocate a separate space on the user's stack for an XSAVE area of whatever size is necessary (as long as it fits in the sigaltstack, if one was provided), and 2. store only a pointer and a length in the mcontext_t with _UC_XSAVE set instead of _UC_FPU. This way existing applications that _don't_ use state beyond the x87 and SSE registers will still be able to, e.g., examine the FXSAVE state in the mcontext_t with SA_SIGINFO signal handlers. But applications that _do_ use that state will see it preserved by signal handlers. XXX if there's not enough space in the sigaltstack, should we crash on SIGILL (what this patch currently does) or just zero out the non-FXSAVE registers? XXX explain ABI issue with getcontext(2) XXX explain ABI issue with MINSIGSTKSZ PR kern/60426: Signal handler corrupts AVX (YMM) registers diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/amd64/amd64/machdep.c --- a/sys/arch/amd64/amd64/machdep.c Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/amd64/amd64/machdep.c Thu Jul 09 05:32:36 2026 +0000 @@ -329,6 +329,11 @@ void init_bootspace(void); void init_slotspace(void); void init_x86_64(paddr_t); +static void cpu_getmcontext_base(struct lwp *, mcontext_t *, unsigned *); +static void cpu_getmcontext_fxsave(struct lwp *, mcontext_t *, unsigned *); +static int cpu_getmcontext_xsave(struct lwp *, mcontext_t *, unsigned *, + char **, bool); + /* * Machine-dependent startup code */ @@ -604,16 +609,23 @@ sendsig_siginfo(const ksiginfo_t *ksi, c (SIGACTION(p, sig).sa_flags & SA_ONSTACK) != 0; /* Allocate space for the signal handler context. */ - if (onstack) + if (onstack) { + KASSERT(l->l_sigstk.ss_size >= MINSIGSTKSZ); sp = ((char *)l->l_sigstk.ss_sp + l->l_sigstk.ss_size); - else + } else { /* AMD64 ABI 128-bytes "red zone". */ sp = (char *)tf->tf_rsp - 128; - + } + + __CTASSERT(8 + STACK_ALIGNBYTES + sizeof(struct sigframe_siginfo) <= + MINSIGSTKSZ); sp -= sizeof(struct sigframe_siginfo); + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); /* Round down the stackpointer to a multiple of 16 for the ABI. */ - fp = (struct sigframe_siginfo *)(((unsigned long)sp & - ~STACK_ALIGNBYTES) - 8); + sp = (char *)(((uintptr_t)sp & ~STACK_ALIGNBYTES) - 8); + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + fp = (struct sigframe_siginfo *)sp; + KASSERT(!onstack || (char *)fp >= (char *)l->l_sigstk.ss_sp); memset(&frame, 0, sizeof(frame)); frame.sf_ra = (uint64_t)ps->sa_sigdesc[sig].sd_tramp; @@ -626,9 +638,33 @@ sendsig_siginfo(const ksiginfo_t *ksi, c sendsig_reset(l, sig); mutex_exit(p->p_lock); - cpu_getmcontext(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * Initialize the model-independent CPU mcontext. + */ + cpu_getmcontext_base(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + + /* + * Find whether we need to use a separate XSAVE area, or we can + * get by with FXSAVE as expected by older programs. If the + * process has actually used the high halves of any YMM + * registers, for example, XSAVE is necessary, but if it + * hasn't, use the more compatible FXSAVE format. + */ + if (!process_xsave_needed_p(l)) { + cpu_getmcontext_fxsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + } else { + error = cpu_getmcontext_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, &sp, onstack); + if (error != 0) + goto relock; + } + /* Copyout all the fp regs, the signal handler might expect them. */ error = copyout(&frame, fp, sizeof frame); +relock: mutex_enter(p->p_lock); if (error != 0) { @@ -2083,6 +2119,14 @@ cpu_reset(void) void cpu_getmcontext(struct lwp *l, mcontext_t *mcp, unsigned int *flags) { + + cpu_getmcontext_base(l, mcp, flags); + cpu_getmcontext_fxsave(l, mcp, flags); +} + +static void +cpu_getmcontext_base(struct lwp *l, mcontext_t *mcp, unsigned int *flags) +{ const struct trapframe *tf = l->l_md.md_regs; __greg_t ras_rip; @@ -2122,26 +2166,130 @@ cpu_getmcontext(struct lwp *l, mcontext_ mcp->_mc_tlsbase = (uintptr_t)l->l_private; *flags |= _UC_TLSBASE; - - process_read_fpregs_xmm(l, (struct fxsave *)&mcp->__fpregs); +} + +/* + * cpu_getmcontext_fxsave(l, mcp, flags) + * + * Fill the fpregs part of mcp with the x87/SSE registers which + * always exist on amd64 in FXSAVE format, and set _UC_FPU in + * flags. Never fails. + */ +static void +cpu_getmcontext_fxsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags) +{ + + process_read_fpregs_xmm(l, (struct fxsave *)mcp->__fpregs.__fxsave); *flags |= _UC_FPU; } +/* + * cpu_getmcontext_xsave(l, mcp, flags, spp, onstack) + * + * Reserve space in the user's stack below *spp for l's XSAVE + * area, set mcp to point there, copy it out there, and set + * _UC_XSAVE in flags. May fail if there's not enough space in + * the stack, when sigaltstack is in play, or if the copyout + * fails. + */ +static int +cpu_getmcontext_xsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags, + char **spp, bool onstack) +{ + const struct xsave_header *xsavebuf; + size_t xsavelen; + int error; + +#define XSAVE_ALIGN 64 /* XXX move me */ + + /* + * Get the kernel's XSAVE area and its length. + */ + process_read_xsave(l, &xsavebuf, &xsavelen); + + /* + * If we're copying into a sigaltstack, make sure we have + * enough space on the stack for the XSAVE area, aligned to 64 + * bytes, before writing anywhere. + */ + if (onstack && (*spp - (char *)l->l_sigstk.ss_sp < xsavelen)) + return ENOSPC; + *spp -= xsavelen; + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + if (onstack && + (((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)) < + (uintptr_t)l->l_sigstk.ss_sp)) + return ENOSPC; + *spp = (char *)((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)); + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)*spp & (XSAVE_ALIGN - 1)) == 0); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, *spp, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the XSAVE area in the mcontext_t. + */ + mcp->__fpregs.__xsave.__xsaveptr = (__greg_t)*spp; + mcp->__fpregs.__xsave.__xsavelen = (__greg_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext will be able to + * restore state from the XSAVE area. + */ + KASSERT((*flags & _UC_FPU) == 0); + *flags |= _UC_XSAVE; + + /* Success! */ + return 0; +} + int cpu_setmcontext(struct lwp *l, const mcontext_t *mcp, unsigned int flags) { struct trapframe *tf = l->l_md.md_regs; const __greg_t *gr = mcp->__gregs; struct proc *p = l->l_proc; + struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; int error; int64_t rflags; CTASSERT(sizeof (mcontext_t) == 26 * 8 + 8 + 512); + /* + * If there's an external XSAVE area, copy it in and validate + * it before we irreversibly modify the trapframe. + * + * We could check the length against the state components + * included, but we currently don't: if it's truncated, it will + * be as if the truncated part were zero-filled -- this is + * implemented in process_write_xsave, called a little below. + */ + if ((flags & (_UC_FPU|_UC_XSAVE)) == _UC_XSAVE) { + const __greg_t xsaveptr = mcp->__fpregs.__xsave.__xsaveptr; + + xsavelen = mcp->__fpregs.__xsave.__xsavelen; + error = process_verify_xsavelen(l, xsavelen); + if (error != 0) + goto out; + xsavebuf = kmem_alloc(xsavelen, KM_SLEEP); + error = copyin((const void *)xsaveptr, xsavebuf, xsavelen); + if (error != 0) + goto out; + error = process_verify_xsave(l, xsavebuf, xsavelen); + if (error != 0) + goto out; + } + if ((flags & _UC_CPU) != 0) { error = cpu_mcontext_validate(l, mcp); if (error != 0) - return error; + goto out; tf->tf_rdi = gr[_REG_RDI]; tf->tf_rsi = gr[_REG_RSI]; @@ -2175,8 +2323,13 @@ cpu_setmcontext(struct lwp *l, const mco l->l_md.md_flags |= MDL_IRET; } - if ((flags & _UC_FPU) != 0) - process_write_fpregs_xmm(l, (const struct fxsave *)&mcp->__fpregs); + if ((flags & _UC_FPU) != 0) { + process_write_fpregs_xmm(l, + (const struct fxsave *)&mcp->__fpregs.__fxsave); + } else if ((flags & _UC_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); + } if ((flags & _UC_TLSBASE) != 0) lwp_setprivate(l, (void *)(uintptr_t)mcp->_mc_tlsbase); @@ -2188,7 +2341,12 @@ cpu_setmcontext(struct lwp *l, const mco l->l_sigstk.ss_flags &= ~SS_ONSTACK; mutex_exit(p->p_lock); - return 0; + /* Success! */ + error = 0; + +out: if (xsavebuf) + kmem_free(xsavebuf, xsavelen); + return error; } int diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/amd64/amd64/netbsd32_machdep.c --- a/sys/arch/amd64/amd64/netbsd32_machdep.c Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/amd64/amd64/netbsd32_machdep.c Thu Jul 09 05:32:36 2026 +0000 @@ -111,6 +111,11 @@ int check_sigcontext32(struct lwp *, con void netbsd32_buildcontext(struct lwp *, struct trapframe *, void *, sig_t, int); +static void cpu_getmcontext32_base(struct lwp *, mcontext32_t *, unsigned *); +static void cpu_getmcontext32_fxsave(struct lwp *, mcontext32_t *, unsigned *); +static int cpu_getmcontext32_xsave(struct lwp *, mcontext32_t *, unsigned *, + char **, bool); + #ifdef EXEC_AOUT /* * There is no native a.out -- this function is required @@ -262,8 +267,33 @@ netbsd32_sendsig_siginfo(const ksiginfo_ sendsig_reset(l, sig); mutex_exit(p->p_lock); - cpu_getmcontext32(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * Initialize the model-independent CPU mcontext. + */ + cpu_getmcontext32_base(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + + /* + * Find whether we need to use a separate XSAVE area, or we can + * get by with FXSAVE as expected by older programs. If the + * process has actually used the high halves of any YMM + * registers, for example, XSAVE is necessary, but if it + * hasn't, use the more compatible FXSAVE format. + */ + if (!process_xsave_needed_p(l)) { + cpu_getmcontext32_fxsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + } else { + char *sp = (void *)fp; /* XXX hoist me up */ + error = cpu_getmcontext32_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, &sp, onstack); + if (error != 0) + goto relock; + } + error = copyout(&frame, fp, sizeof(frame)); +relock: mutex_enter(p->p_lock); if (error != 0) { @@ -798,8 +828,37 @@ cpu_setmcontext32(struct lwp *l, const m struct trapframe *tf = l->l_md.md_regs; const __greg32_t *gr = mcp->__gregs; struct proc *p = l->l_proc; + struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; int error; + /* + * If there's an external XSAVE area, copy it in and validate + * it before we irreversibly modify the trapframe. + * + * We could check the length against the state components + * included, but we currently don't: if it's truncated, it will + * be as if the truncated part were zero-filled -- this is + * implemented in process_write_xsave, called a little below. + */ + if ((flags & (_UC_FPU|_UC_XSAVE)) == _UC_XSAVE) { + const __greg32_t xsaveptr = + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr; + + xsavelen = mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen; + error = process_verify_xsavelen(l, xsavelen); + if (error != 0) + goto out; + xsavebuf = kmem_alloc(xsavelen, KM_SLEEP); + error = copyin((const void *)(uintptr_t)xsaveptr, xsavebuf, + xsavelen); + if (error != 0) + goto out; + error = process_verify_xsave(l, xsavebuf, xsavelen); + if (error != 0) + goto out; + } + /* Restore register context, if any. */ if ((flags & _UC_CPU) != 0) { /* @@ -836,6 +895,9 @@ cpu_setmcontext32(struct lwp *l, const m /* Assume fxsave context */ process_write_fpregs_xmm(l, (const struct fxsave *) &mcp->__fpregs.__fp_reg_set.__fp_xmm_state); + } else if ((flags & _UC_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); } mutex_enter(p->p_lock); @@ -845,12 +907,25 @@ cpu_setmcontext32(struct lwp *l, const m l->l_sigstk.ss_flags &= ~SS_ONSTACK; mutex_exit(p->p_lock); - return 0; + /* Success! */ + error = 0; + +out: if (xsavebuf) + kmem_free(xsavebuf, xsavelen); + return error; } void cpu_getmcontext32(struct lwp *l, mcontext32_t *mcp, unsigned int *flags) { + + cpu_getmcontext32_base(l, mcp, flags); + cpu_getmcontext32_fxsave(l, mcp, flags); +} + +static void +cpu_getmcontext32_base(struct lwp *l, mcontext32_t *mcp, unsigned int *flags) +{ const struct trapframe *tf = l->l_md.md_regs; __greg32_t *gr = mcp->__gregs; __greg32_t ras_eip; @@ -884,6 +959,18 @@ cpu_getmcontext32(struct lwp *l, mcontex mcp->_mc_tlsbase = (uint32_t)(uintptr_t)l->l_private; *flags |= _UC_TLSBASE; +} + +/* + * cpu_getmcontext32_fxsave(l, mcp, flags) + * + * Fill the fpregs part of mcp with the x87/SSE registers in + * FXSAVE format, and set _UC_FXSAVE|_UC_FPU in flags. Never + * fails. + */ +static void +cpu_getmcontext32_fxsave(struct lwp *l, mcontext32_t *mcp, unsigned int *flags) +{ /* Save floating point register context. */ process_read_fpregs_xmm(l, (struct fxsave *) @@ -892,6 +979,77 @@ cpu_getmcontext32(struct lwp *l, mcontex *flags |= _UC_FXSAVE | _UC_FPU; } +/* + * cpu_getmcontext32_xsave(l, mcp, flags, spp, onstack) + * + * Reserve space in the user's stack below *spp for l's XSAVE + * area, set mcp to point there, copy it out there, and set + * _UC_XSAVE in flags. May fail if there's not enough space in + * the stack, when sigaltstack is in play, or if the copyout + * fails. + */ +static int +cpu_getmcontext32_xsave(struct lwp *l, mcontext32_t *mcp, unsigned int *flags, + char **spp, bool onstack) +{ + const struct xsave_header *xsavebuf; + size_t xsavelen; + int error; + + KASSERT((uintptr_t)*spp == (__greg32_t)(uintptr_t)*spp); + +#define XSAVE_ALIGN 64 /* XXX move me */ + + /* + * Get the kernel's XSAVE area and its length. + */ + process_read_xsave(l, &xsavebuf, &xsavelen); + KASSERTMSG(xsavelen <= ~(__greg32_t)0, "excessive XSAVE length: %zu", + xsavelen); + + /* + * If we're copying into a sigaltstack, make sure we have + * enough space on the stack for the XSAVE area, aligned to 64 + * bytes, before writing anywhere. + */ + if (onstack && (*spp - (char *)l->l_sigstk.ss_sp < xsavelen)) + return ENOSPC; + *spp -= xsavelen; + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + if (onstack && + (((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)) < + (uintptr_t)l->l_sigstk.ss_sp)) + return ENOSPC; + *spp = (char *)((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)); + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)*spp & (XSAVE_ALIGN - 1)) == 0); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, *spp, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the XSAVE area in the mcontext32_t. + */ + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr = + (__greg32_t)(uintptr_t)*spp; + mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen = + (__greg32_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext32 will be able to + * restore state from the XSAVE area. + */ + KASSERT((*flags & (_UC_FPU|_UC_FXSAVE)) == 0); + *flags |= _UC_XSAVE; + + /* Success! */ + return 0; +} + void startlwp32(void *arg) { diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/amd64/include/mcontext.h --- a/sys/arch/amd64/include/mcontext.h Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/amd64/include/mcontext.h Thu Jul 09 05:32:36 2026 +0000 @@ -56,7 +56,13 @@ typedef __greg_t __gregset_t[_NGREG]; * which requires 16 byte alignment. However the mcontext version * is never directly accessed. */ -typedef char __fpregset_t[512] __aligned(8); +typedef union { + char __fxsave[512] __aligned(8); + struct { + __greg_t __xsaveptr; + __greg_t __xsavelen; + } __xsave; +} __fpregset_t; typedef struct { __gregset_t __gregs; @@ -75,6 +81,7 @@ typedef struct { #define _UC_MACHINE_SET_PC(uc, pc) _UC_MACHINE_PC(uc) = (pc) #define _UC_TLSBASE _UC_MD_BIT19 +#define _UC_XSAVE _UC_MD_BIT20 /* * mcontext extensions to handle signal delivery. @@ -127,6 +134,10 @@ typedef struct { struct { char __fp_xmm[512]; } __fp_xmm_state; + struct { + __greg32_t __xsaveptr; + __greg32_t __xsavelen; + } __xsave; } __fp_reg_set; int __fp_pad[33]; /* Historic padding */ } __fpregset32_t; diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/i386/i386/machdep.c --- a/sys/arch/i386/i386/machdep.c Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/i386/i386/machdep.c Thu Jul 09 05:32:36 2026 +0000 @@ -253,6 +253,11 @@ void initgdt(union descriptor *); static void i386_proc0_pcb_ldt_init(void); +static void cpu_getmcontext_base(struct lwp *, mcontext_t *, unsigned *); +static void cpu_getmcontext_fxsave(struct lwp *, mcontext_t *, unsigned *); +static int cpu_getmcontext_xsave(struct lwp *, mcontext_t *, unsigned *, + char **, bool); + int *esym; int *eblob; extern int boothowto; @@ -693,8 +698,33 @@ sendsig_siginfo(const ksiginfo_t *ksi, c sendsig_reset(l, sig); mutex_exit(p->p_lock); - cpu_getmcontext(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * Initialize the model-independent CPU mcontext. + */ + cpu_getmcontext_base(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + + /* + * Find whether we need to use a separate XSAVE area, or we can + * get by with FXSAVE as expected by older programs. If the + * process has actually used the high halves of any YMM + * registers, for example, XSAVE is necessary, but if it + * hasn't, use the more compatible FXSAVE format. + */ + if (!process_xsave_needed_p(l)) { + cpu_getmcontext_fxsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags); + } else { + char *sp = (void *)fp; /* XXX */ + error = cpu_getmcontext_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, &sp, onstack); + if (error != 0) + goto relock; + } + error = copyout(&frame, fp, sizeof(frame)); +relock: mutex_enter(p->p_lock); if (error != 0) { @@ -1608,6 +1638,14 @@ cpu_reset(void) void cpu_getmcontext(struct lwp *l, mcontext_t *mcp, unsigned int *flags) { + + cpu_getmcontext_base(l, mcp, flags); + cpu_getmcontext_fxsave(l, mcp, flags); +} + +static void +cpu_getmcontext_base(struct lwp *l, mcontext_t *mcp, unsigned int *flags) +{ const struct trapframe *tf = l->l_md.md_regs; __greg_t *gr = mcp->__gregs; __greg_t ras_eip; @@ -1642,7 +1680,18 @@ cpu_getmcontext(struct lwp *l, mcontext_ mcp->_mc_tlsbase = (uintptr_t)l->l_private; *flags |= _UC_TLSBASE; +} +/* + * cpu_getmcontext32_fxsave(l, mcp, flags) + * + * Fill the fpregs part of mcp with the x87/SSE registers in + * FXSAVE format, and set _UC_FXSAVE|_UC_FPU in flags. Never + * fails. + */ +static void +cpu_getmcontext_fxsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags) +{ /* * Save floating point register context. * @@ -1660,6 +1709,71 @@ cpu_getmcontext(struct lwp *l, mcontext_ *flags |= _UC_FXSAVE | _UC_FPU; } +/* + * cpu_getmcontext_xsave(l, mcp, flags, spp, onstack) + * + * Reserve space in the user's stack below *spp for l's XSAVE + * area, set mcp to point there, copy it out there, and set + * _UC_XSAVE in flags. May fail if there's not enough space in + * the stack, when sigaltstack is in play, or if the copyout + * fails. + */ +static int +cpu_getmcontext_xsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags, + char **spp, bool onstack) +{ + const struct xsave_header *xsavebuf; + size_t xsavelen; + int error; + +#define XSAVE_ALIGN 64 /* XXX move me */ + + /* + * Get the kernel's XSAVE area and its length. + */ + process_read_xsave(l, &xsavebuf, &xsavelen); + + /* + * If we're copying into a sigaltstack, make sure we have + * enough space on the stack for the XSAVE area, aligned to 64 + * bytes, before writing anywhere. + */ + if (onstack && (*spp - (char *)l->l_sigstk.ss_sp < xsavelen)) + return ENOSPC; + *spp -= xsavelen; + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + if (onstack && + (((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)) < + (uintptr_t)l->l_sigstk.ss_sp)) + return ENOSPC; + *spp = (char *)((uintptr_t)*spp & ~(XSAVE_ALIGN - 1)); + KASSERT(!onstack || *spp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)*spp & (XSAVE_ALIGN - 1)) == 0); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, *spp, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the XSAVE area in the mcontext_t. + */ + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr = (__greg_t)*spp; + mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen = (__greg_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext will be able to + * restore state from the XSAVE area. + */ + KASSERT((*flags & (_UC_FPU|_UC_FXSAVE)) == 0); + *flags |= _UC_XSAVE; + + /* Success! */ + return 0; +} + int cpu_mcontext_validate(struct lwp *l, const mcontext_t *mcp) { @@ -1686,8 +1800,36 @@ cpu_setmcontext(struct lwp *l, const mco struct trapframe *tf = l->l_md.md_regs; const __greg_t *gr = mcp->__gregs; struct proc *p = l->l_proc; + struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; int error; + /* + * If there's an external XSAVE area, copy it in and validate + * it before we irreversibly modify the trapframe. + * + * We could check the length against the state components + * included, but we currently don't: if it's truncated, it will + * be as if the truncated part were zero-filled -- this is + * implemented in process_write_xsave, called a little below. + */ + if ((flags & (_UC_FPU|_UC_XSAVE)) == _UC_XSAVE) { + const __greg_t xsaveptr = + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr; + + xsavelen = mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen; + error = process_verify_xsavelen(l, xsavelen); + if (error != 0) + goto out; + xsavebuf = kmem_alloc(xsavelen, KM_SLEEP); + error = copyin((const void *)xsaveptr, xsavebuf, xsavelen); + if (error != 0) + goto out; + error = process_verify_xsave(l, xsavebuf, xsavelen); + if (error != 0) + goto out; + } + /* Restore register context, if any. */ if ((flags & _UC_CPU) != 0) { error = cpu_mcontext_validate(l, mcp); @@ -1732,6 +1874,9 @@ cpu_setmcontext(struct lwp *l, const mco process_write_fpregs_s87(l, (const struct save87 *) &mcp->__fpregs.__fp_reg_set.__fpchip_state); } + } else if ((flags & _UC_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); } mutex_enter(p->p_lock); @@ -1740,7 +1885,13 @@ cpu_setmcontext(struct lwp *l, const mco if (flags & _UC_CLRSTACK) l->l_sigstk.ss_flags &= ~SS_ONSTACK; mutex_exit(p->p_lock); - return (0); + + /* Success! */ + error = 0; + +out: if (xsavebuf) + kmem_free(xsavebuf, xsavelen); + return error; } #define DEV_IO 14 /* iopl for compat_10 */ diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/i386/include/mcontext.h --- a/sys/arch/i386/include/mcontext.h Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/i386/include/mcontext.h Thu Jul 09 05:32:36 2026 +0000 @@ -40,6 +40,7 @@ #define _UC_CLRSTACK _UC_MD_BIT17 #define _UC_VM _UC_MD_BIT18 #define _UC_TLSBASE _UC_MD_BIT19 +#define _UC_XSAVE _UC_MD_BIT20 /* * Layout of mcontext_t according to the System V Application Binary Interface, @@ -85,6 +86,10 @@ typedef struct { char __fp_xmm[512]; } __fp_xmm_state; /* x87 and xmm regs in fxsave format */ int __fp_fpregs[128]; + struct { + __greg_t __xsaveptr; + __greg_t __xsavelen; + } __xsave; } __fp_reg_set; int __fp_pad[33]; /* Historic padding */ } __fpregset_t; diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/x86/include/fpu.h --- a/sys/arch/x86/include/fpu.h Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/x86/include/fpu.h Thu Jul 09 05:32:36 2026 +0000 @@ -46,6 +46,12 @@ int process_read_xstate(struct lwp *, st int process_verify_xstate(const struct xstate *); int process_write_xstate(struct lwp *, const struct xstate *); +bool process_xsave_needed_p(struct lwp *); +void process_read_xsave(struct lwp *, const struct xsave_header **, size_t *); +int process_verify_xsavelen(struct lwp *, size_t); +int process_verify_xsave(struct lwp *, const struct xsave_header *, size_t); +void process_write_xsave(struct lwp *, const struct xsave_header *, size_t); + #endif #endif /* _X86_FPU_H_ */ diff -r 15c667d8eac5 -r e5e730145a70 sys/arch/x86/x86/fpu.c --- a/sys/arch/x86/x86/fpu.c Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/x86/x86/fpu.c Thu Jul 09 05:32:36 2026 +0000 @@ -1039,3 +1039,128 @@ process_write_xstate(struct lwp *l, cons return 0; } + +/* + * process_xsave_needed_p(l) + * + * True if l's FPU state requires XSAVE, false if it can be + * faithfully saved and restored with only FXSAVE at most. Since + * FXSAVE has been available for longer in mcontext_t (and thus + * more likely to be understood by userland software), and + * requires no external buffer for userland mcontext_t, it is + * preferable to use FXSAVE where possible instead of XSAVE. + */ +bool +process_xsave_needed_p(struct lwp *l) +{ + union savefpu *fpu_save; + + /* + * If we're not using XSAVE at all on this CPU, then this + * thread doesn't require it. + */ + if (x86_fpu_save < FPU_SAVE_XSAVE) + return false; + + /* + * If we only use x87 and SSE state on this CPU, then this + * thread doesn't require XSAVE -- FXSAVE is good enough. + */ + if ((x86_xsave_features & ~(XCR0_X87|XCR0_SSE)) == 0) + return false; + + /* + * If _this thread_ has yet to use any state other than x87 or + * SSE, then it doesn't require XSAVE -- FXSAVE is good enough. + */ + fpu_save = fpu_lwp_area(l); + if ((fpu_save->sv_xsave_hdr.xsh_xstate_bv & ~(XCR0_X87|XCR0_SSE)) == 0) + return false; + return true; +} + +/* + * process_read_xsave(l, &xsavebuf, &xsavelen) + * + * Set xsavebuf to l's current XSAVE area, and xsavelen to the + * size of that area to copy out to userland. It can later be + * restored with process_write_xsave. + */ +void +process_read_xsave(struct lwp *l, const struct xsave_header **xsavebufp, + size_t *xsavelenp) +{ + union savefpu *area = fpu_lwp_area(l); + + *xsavebufp = &area->sv_xsave_hdr; + KASSERT(((*xsavebufp)->xsh_xstate_bv & ~x86_xsave_features) == 0); + + /* + * XXX Consider shrinking this to just the components that are + * actually represented in XSTATE_BV. + */ + *xsavelenp = x86_fpu_save_size; +} + +/* + * process_verify_xsavelen(l, xsavelen) + * + * Verify that a putative XSAVE area length is plausible, before + * copying that many bytes in from userland. Return 0 on success, + * nonzero error code on failure. + */ +int +process_verify_xsavelen(struct lwp *l, size_t xsavelen) +{ + + if (xsavelen < sizeof(struct xsave_header)) + return EINVAL; + if (xsavelen > x86_fpu_save_size) + return EINVAL; + return 0; +} + +/* + * process_verify_xsave(l, xsavebuf, xsavelen) + * + * Verify that a putative XSAVE area from userland is valid. + * Return 0 on success, nonzero error code on failure. + */ +int +process_verify_xsave(struct lwp *l, const struct xsave_header *xsavebuf, + size_t xsavelen) +{ + + if (xsavelen < sizeof(*xsavebuf)) + return EINVAL; + if ((xsavebuf->xsh_xstate_bv & ~x86_xsave_features) != 0) + return EINVAL; + /* + * XXX Consider verifying that any components claimed present + * are actually there within xsavelen. Not a big deal if they + * aren't, though: we will just zero-fill them, so it is as if + * they were there but all zero. + */ + return 0; +} + +/* + * process_write_xsave(l, xsavebuf, xsavelen) + * + * Given an XSAVE area copied in from userland, load l's FPU state + * from that area. If xstatelen is shorter than the CPU's + * x86_fpu_save_size, zero-fill it. Caller must validate it first + * with process_verify_xsave. + */ +void +process_write_xsave(struct lwp *l, const struct xsave_header *xsavebuf, + size_t xsavelen) +{ + union savefpu *area = fpu_lwp_area(l); + + KASSERT(xsavelen <= x86_fpu_save_size); + KASSERT((xsavebuf->xsh_xstate_bv & ~x86_xsave_features) == 0); + + memcpy(area, xsavebuf, xsavelen); + memset((char *)area + xsavelen, 0, x86_fpu_save_size - xsavelen); +} diff -r 15c667d8eac5 -r e5e730145a70 tests/kernel/t_signal_and_fpu.c --- a/tests/kernel/t_signal_and_fpu.c Thu Jul 09 02:01:07 2026 +0000 +++ b/tests/kernel/t_signal_and_fpu.c Thu Jul 09 05:32:36 2026 +0000 @@ -384,8 +384,7 @@ ATF_TC_HEAD(ymm, tc) } ATF_TC_BODY(ymm, tc) { - test_signal_fpu(&ymm_supported, &test_ymm, &trash_ymm, - "PR kern/60426: Signal handler corrupts AVX (YMM) registers"); + test_signal_fpu(&ymm_supported, &test_ymm, &trash_ymm, NULL); } #endif