diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 2026 +0000 @@ -329,6 +329,9 @@ void init_bootspace(void); void init_slotspace(void); void init_x86_64(paddr_t); +static int cpu_getmcontext_xsave(struct lwp *, mcontext_t *, unsigned *, + const struct xsave_header *, size_t, struct xsave_header *); + /* * Machine-dependent startup code */ @@ -594,6 +597,9 @@ sendsig_siginfo(const ksiginfo_t *ksi, c struct sigframe_siginfo *fp, frame; sig_t catcher = SIGACTION(p, sig).sa_handler; struct trapframe *tf = l->l_md.md_regs; + const struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; + struct xsave_header *user_xsave = NULL; char *sp; KASSERT(mutex_owned(p->p_lock)); @@ -604,16 +610,67 @@ 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; - + } + + /* + * The maximum amount of space we might use, including padding + * for alignment, had better fit in MINSIGSTKSZ. + * + * If this changes because you have increased XSAVE_MAX_BYTES, + * you need to work out the ABI change for MINSIGSTKSZ. + */ + __CTASSERT(8 + STACK_ALIGNBYTES + sizeof(struct sigframe_siginfo) + + (XSAVE_ALIGN - 1) + XSAVE_MAX_BYTES <= MINSIGSTKSZ); + + /* + * Find whether we need to allocate a separate XSAVE area, + * because the user program has used extended CPU state beyond + * the x87/SSE registers, or whether we can get by with just an + * FXSAVE area. + */ + if (process_xsave_needed_p(l)) { + process_read_xsave(l, &xsavebuf, &xsavelen); + KASSERT(xsavebuf != NULL); + KASSERT(xsavelen <= XSAVE_MAX_BYTES); + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || + sp - (char *)l->l_sigstk.ss_sp >= xsavelen); + sp -= xsavelen; + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || + sp - (char *)l->l_sigstk.ss_sp >= XSAVE_ALIGN - 1); + sp = (char *)((uintptr_t)sp & ~(XSAVE_ALIGN - 1)); + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)sp & (XSAVE_ALIGN - 1)) == 0); + user_xsave = (void *)sp; + } + + /* + * Reserve space for a struct sigframe_siginfo. The first + * member is a return address, as if we are entering a + * procedure with a CALL instruction with a stack frame aligned + * to a multiple of 16 bytes; the rest of the structure is that + * stack frame. So make sure the structure starts at an + * address congruent to 8 modulo 16. + */ + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || (size_t)(sp - (char *)l->l_sigstk.ss_sp) >= + 8 + STACK_ALIGNBYTES + sizeof(struct sigframe_siginfo)); sp -= sizeof(struct sigframe_siginfo); /* Round down the stackpointer to a multiple of 16 for the ABI. */ fp = (struct sigframe_siginfo *)(((unsigned long)sp & ~STACK_ALIGNBYTES) - 8); + KASSERT(!onstack || (char *)fp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)fp & STACK_ALIGNBYTES) == 8); memset(&frame, 0, sizeof(frame)); frame.sf_ra = (uint64_t)ps->sa_sigdesc[sig].sd_tramp; @@ -627,8 +684,21 @@ sendsig_siginfo(const ksiginfo_t *ksi, c mutex_exit(p->p_lock); cpu_getmcontext(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * If we have to use XSAVE, copy out that area separately -- + * and be ready to bail if it failed. + */ + if (xsavebuf) { + error = cpu_getmcontext_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, xsavebuf, xsavelen, user_xsave); + 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) { @@ -2123,25 +2193,95 @@ 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); + process_read_fpregs_xmm(l, (struct fxsave *)&mcp->__fpregs.__fxsave); *flags |= _UC_FPU; } +/* + * cpu_getmcontext_xsave(l, mcp, flags, xsavebuf, xsavelen, user_xsave) + * + * Copy out xsavebuf[0..xsavelen) to user_xsave, set mcp to point + * there, and set _UC_XSAVE in flags. Caller must have already + * used cpu_getmcontext to initialize mcp's FXSAVE area. + * + * May fail if the copyout fails. + */ +static int +cpu_getmcontext_xsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags, + const struct xsave_header *xsavebuf, size_t xsavelen, + struct xsave_header *user_xsave) +{ + int error; + + KASSERT(*flags & _UC_FPU); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, user_xsave, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the real XSAVE area in the + * architecturally unused bits mcontext_t's FXSAVE area. + */ + mcp->__fpregs.__xsave.__xsaveptr = (__greg_t)(uintptr_t)user_xsave; + mcp->__fpregs.__xsave.__xsavelen = (__greg_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext will be able to + * restore the full state from the XSAVE area. + */ + *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_XSAVE) { + const struct xsave_header *user_xsave = + (void *)(uintptr_t)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(user_xsave, 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 +2315,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_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); + } else if ((flags & _UC_FPU) != 0) { + process_write_fpregs_xmm(l, + (const struct fxsave *)&mcp->__fpregs.__fxsave); + } if ((flags & _UC_TLSBASE) != 0) lwp_setprivate(l, (void *)(uintptr_t)mcp->_mc_tlsbase); @@ -2188,7 +2333,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 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 Fri Jul 10 02:39:17 2026 +0000 @@ -111,6 +111,9 @@ int check_sigcontext32(struct lwp *, con void netbsd32_buildcontext(struct lwp *, struct trapframe *, void *, sig_t, int); +static int cpu_getmcontext32_xsave(struct lwp *, mcontext32_t *, unsigned *, + const struct xsave_header *, size_t, struct xsave_header *); + #ifdef EXEC_AOUT /* * There is no native a.out -- this function is required @@ -219,6 +222,10 @@ netbsd32_sendsig_siginfo(const ksiginfo_ sig_t catcher = sa->sa_handler; struct trapframe *tf = l->l_md.md_regs; stack_t * const ss = &l->l_sigstk; + const struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; + struct xsave_header *user_xsave = NULL; + char *sp; /* Do we need to jump onto the signal stack? */ onstack = @@ -226,15 +233,59 @@ netbsd32_sendsig_siginfo(const ksiginfo_ (sa->sa_flags & SA_ONSTACK) != 0; /* Allocate space for the signal handler context. */ - if (onstack) - fp = (struct netbsd32_sigframe_siginfo *) - ((char *)ss->ss_sp + ss->ss_size); - else - fp = (struct netbsd32_sigframe_siginfo *)tf->tf_rsp; + if (onstack) { + KASSERT(ss->ss_size >= MINSIGSTKSZ); + sp = (char *)ss->ss_sp + ss->ss_size; + } else { + sp = (char *)tf->tf_rsp; + } + + /* + * The maximum amount of space we might use, including padding + * for alignment, had better fit in MINSIGSTKSZ. + * + * If this changes because you have increased XSAVE_MAX_BYTES, + * you need to work out the ABI change for MINSIGSTKSZ. + */ + __CTASSERT(STACK_ALIGNBYTES32 + + sizeof(struct netbsd32_sigframe_siginfo) + + (XSAVE_ALIGN - 1) + XSAVE_MAX_BYTES <= MINSIGSTKSZ); + /* + * Find whether we need to allocate a separate XSAVE area, + * because the user program has used extended CPU state beyond + * the x87/SSE registers, or whether we can get by with just an + * FXSAVE area. + */ + if (process_xsave_needed_p(l)) { + process_read_xsave(l, &xsavebuf, &xsavelen); + KASSERT(xsavebuf != NULL); + KASSERT(xsavelen <= XSAVE_MAX_BYTES); + + KASSERT(!onstack || sp >= (char *)ss->ss_sp); + KASSERT(!onstack || + (size_t)(sp - (char *)ss->ss_sp) >= xsavelen); + sp -= xsavelen; + + KASSERT(!onstack || sp >= (char *)ss->ss_sp); + KASSERT(!onstack || (size_t)(sp - (char *)ss->ss_sp) >= + XSAVE_ALIGN - 1); + sp = (char *)((uintptr_t)sp & ~(XSAVE_ALIGN - 1)); + + KASSERT(!onstack || sp >= (char *)ss->ss_sp); + KASSERT(((uintptr_t)sp & (XSAVE_ALIGN - 1)) == 0); + user_xsave = (void *)sp; + } + + KASSERT(!onstack || sp >= (char *)ss->ss_sp); + KASSERT(!onstack || (size_t)(sp - (char *)ss->ss_sp) >= + STACK_ALIGNBYTES32 + sizeof(struct netbsd32_sigframe_siginfo)); + fp = (struct netbsd32_sigframe_siginfo *)sp; fp--; fp = (struct netbsd32_sigframe_siginfo *)((uintptr_t)fp & ~STACK_ALIGNBYTES32); + KASSERT(!onstack || (char *)fp >= (char *)ss->ss_sp); + KASSERT(((uintptr_t)fp & STACK_ALIGNBYTES32) == 0); /* Build stack frame for signal trampoline. */ switch (ps->sa_sigdesc[sig].sd_vers) { @@ -263,7 +314,20 @@ netbsd32_sendsig_siginfo(const ksiginfo_ mutex_exit(p->p_lock); cpu_getmcontext32(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * If we have to use XSAVE, copy out that area separately -- + * and be ready to bail if it failed. + */ + if (xsavebuf) { + error = cpu_getmcontext32_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, xsavebuf, xsavelen, user_xsave); + if (error != 0) + goto relock; + } + error = copyout(&frame, fp, sizeof(frame)); +relock: mutex_enter(p->p_lock); if (error != 0) { @@ -798,8 +862,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_XSAVE) != 0) { + 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) { /* @@ -832,7 +925,10 @@ cpu_setmcontext32(struct lwp *l, const m lwp_setprivate(l, (void *)(uintptr_t)mcp->_mc_tlsbase); /* Restore floating point register context, if any. */ - if ((flags & _UC_FPU) != 0) { + if ((flags & _UC_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); + } else if ((flags & _UC_FPU) != 0) { /* Assume fxsave context */ process_write_fpregs_xmm(l, (const struct fxsave *) &mcp->__fpregs.__fp_reg_set.__fp_xmm_state); @@ -845,7 +941,12 @@ 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 @@ -892,6 +993,54 @@ cpu_getmcontext32(struct lwp *l, mcontex *flags |= _UC_FXSAVE | _UC_FPU; } +/* + * cpu_getmcontext32_xsave(l, mcp, flags, xsavebuf, xsavelen, user_xsave) + * + * Copy out xsavebuf[0..xsavelen) to user_xsave, set mcp to point + * there, and set _UC_XSAVE in flags. Caller must have already + * used cpu_getmcontext32 to initialize mcp's FXSAVE area. + * + * May fail if the copyout fails. + */ +static int +cpu_getmcontext32_xsave(struct lwp *l, mcontext32_t *mcp, unsigned int *flags, + const struct xsave_header *xsavebuf, size_t xsavelen, + struct xsave_header *user_xsave) +{ + int error; + + KASSERT(*flags & _UC_FPU); + KASSERT(*flags & _UC_FXSAVE); + KASSERT((uintptr_t)user_xsave == (__greg32_t)(uintptr_t)user_xsave); + KASSERT(xsavelen == (__greg32_t)xsavelen); + __CTASSERT(XSAVE_MAX_BYTES <= ~(__greg32_t)0); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, user_xsave, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the real XSAVE area in the + * architecturally unused bits mcontext_t's FXSAVE area. + */ + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr = + (__greg32_t)(uintptr_t)user_xsave; + mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen = + (__greg32_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext will be able to + * restore the full state from the XSAVE area. + */ + *flags |= _UC_XSAVE; + + /* Success! */ + return 0; +} + void startlwp32(void *arg) { diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 2026 +0000 @@ -56,7 +56,28 @@ 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 { + /* + * `The XSAVE feature set does not use bytes 511:416; + * bytes 463:416 are reserved.' + * + * We take a part out of this to form a pointer to an + * external XSAVE area. This way, we can replicate the + * FXSAVE parts for the benefit of userland programs + * that aren't aware of the XSAVE pointer, have used + * the extended CPU registers (ymmN/zmmN/&c.), and want + * to examine the x87/SSE register state in a signal + * handler. The kernel does not use this part. + */ + char __fxsave[416]; + char __rsvd[48]; + __greg_t __xsaveptr; + __greg_t __xsavelen; + char __pad[32]; + } __xsave; +} __fpregset_t; typedef struct { __gregset_t __gregs; @@ -75,6 +96,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 +149,13 @@ typedef struct { struct { char __fp_xmm[512]; } __fp_xmm_state; + struct { + char __fxsave[416]; + char __rsvd[48]; + __greg32_t __xsaveptr; + __greg32_t __xsavelen; + char __pad[40]; + } __xsave; } __fp_reg_set; int __fp_pad[33]; /* Historic padding */ } __fpregset32_t; diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 2026 +0000 @@ -253,6 +253,9 @@ void initgdt(union descriptor *); static void i386_proc0_pcb_ldt_init(void); +static int cpu_getmcontext_xsave(struct lwp *, mcontext_t *, unsigned *, + const struct xsave_header *, size_t, struct xsave_header *); + int *esym; int *eblob; extern int boothowto; @@ -670,11 +673,72 @@ sendsig_siginfo(const ksiginfo_t *ksi, c struct sigacts *ps = p->p_sigacts; int onstack, error; int sig = ksi->ksi_signo; - struct sigframe_siginfo *fp = getframe(l, sig, &onstack), frame; + struct sigframe_siginfo *fp, frame; sig_t catcher = SIGACTION(p, sig).sa_handler; + struct trapframe *tf = l->l_md.md_regs; + const struct xsave_header *xsavebuf = NULL; + size_t xsavelen = 0; + struct xsave_header *user_xsave = NULL; + char *sp; KASSERT(mutex_owned(p->p_lock)); + /* Do we need to jump onto the signal stack? */ + onstack = + (l->l_sigstk.ss_flags & (SS_DISABLE | SS_ONSTACK)) == 0 && + (SIGACTION(p, sig).sa_flags & SA_ONSTACK) != 0; + + /* Allocate space for the signal handler context. */ + if (onstack) { + KASSERT(l->l_sigstk.ss_size >= MINSIGSTKSZ); + sp = ((char *)l->l_sigstk.ss_sp + l->l_sigstk.ss_size); + } else { + sp = (char *)tf->tf_esp; + } + + /* + * The maximum amount of space we might use, including padding + * for alignment, had better fit in MINSIGSTKSZ. + * + * If this changes because you have increased XSAVE_MAX_BYTES, + * you need to work out the ABI change for MINSIGSTKSZ. + */ + __CTASSERT(STACK_ALIGNBYTES + sizeof(struct sigframe_siginfo) + + (XSAVE_ALIGN - 1) + XSAVE_MAX_BYTES <= MINSIGSTKSZ); + + /* + * Find whether we need to allocate a separate XSAVE area, + * because the user program has used extended CPU state beyond + * the x87/SSE registers, or whether we can get by with just an + * FXSAVE area. + */ + if (process_xsave_needed_p(l)) { + process_read_xsave(l, &xsavebuf, &xsavelen); + KASSERT(xsavebuf != NULL); + KASSERT(xsavelen <= XSAVE_MAX_BYTES); + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || + sp - (char *)l->l_sigstk.ss_sp >= xsavelen); + sp -= xsavelen; + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || + sp - (char *)l->l_sigstk.ss_sp >= XSAVE_ALIGN - 1); + sp = (char *)((uintptr_t)sp & ~(XSAVE_ALIGN - 1)); + + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(((uintptr_t)sp & (XSAVE_ALIGN - 1)) == 0); + user_xsave = (void *)sp; + } + + /* + * Reserve space for an aligned struct sigframe_siginfo. + */ + KASSERT(!onstack || sp >= (char *)l->l_sigstk.ss_sp); + KASSERT(!onstack || (size_t)(sp - (char *)l->l_sigstk.ss_sp) >= + STACK_ALIGNBYTES + sizeof(struct sigframe_siginfo)); + fp = (struct sigframe_siginfo *)sp; fp--; fp = (struct sigframe_siginfo *)((uintptr_t)fp & ~STACK_ALIGNBYTES); @@ -694,7 +758,20 @@ sendsig_siginfo(const ksiginfo_t *ksi, c mutex_exit(p->p_lock); cpu_getmcontext(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags); + + /* + * If we have to use XSAVE, copy out that area separately -- + * and be ready to bail if it failed. + */ + if (xsavebuf) { + error = cpu_getmcontext_xsave(l, &frame.sf_uc.uc_mcontext, + &frame.sf_uc.uc_flags, xsavebuf, xsavelen, user_xsave); + if (error != 0) + goto relock; + } + error = copyout(&frame, fp, sizeof(frame)); +relock: mutex_enter(p->p_lock); if (error != 0) { @@ -1660,6 +1737,50 @@ cpu_getmcontext(struct lwp *l, mcontext_ *flags |= _UC_FXSAVE | _UC_FPU; } +/* + * cpu_getmcontext_xsave(l, mcp, flags, xsavebuf, xsavelen, user_xsave) + * + * Copy out xsavebuf[0..xsavelen) to user_xsave, set mcp to point + * there, and set _UC_XSAVE in flags. Caller must have already + * used cpu_getmcontext to initialize mcp's FXSAVE area. + * + * May fail if the copyout fails. + */ +static int +cpu_getmcontext_xsave(struct lwp *l, mcontext_t *mcp, unsigned int *flags, + const struct xsave_header *xsavebuf, size_t xsavelen, + struct xsave_header *user_xsave) +{ + int error; + + KASSERT(*flags & _UC_FPU); + KASSERT(*flags & _UC_FXSAVE); + + /* + * Copy out the XSAVE area. + */ + error = copyout(xsavebuf, user_xsave, xsavelen); + if (error != 0) + return error; + + /* + * Record a pointer to the real XSAVE area in the + * architecturally unused bits mcontext_t's FXSAVE area. + */ + mcp->__fpregs.__fp_reg_set.__xsave.__xsaveptr = + (__greg_t)(uintptr_t)user_xsave; + mcp->__fpregs.__fp_reg_set.__xsave.__xsavelen = (__greg_t)xsavelen; + + /* + * Set the _UC_XSAVE flag so cpu_setmcontext will be able to + * restore the full state from the XSAVE area. + */ + *flags |= _UC_XSAVE; + + /* Success! */ + return 0; +} + int cpu_mcontext_validate(struct lwp *l, const mcontext_t *mcp) { @@ -1686,8 +1807,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_XSAVE) != 0) { + const __greg_t user_xsave = + 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 *)user_xsave, 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); @@ -1719,7 +1868,10 @@ cpu_setmcontext(struct lwp *l, const mco lwp_setprivate(l, (void *)(uintptr_t)mcp->_mc_tlsbase); /* Restore floating point register context, if given. */ - if ((flags & _UC_FPU) != 0) { + if ((flags & _UC_XSAVE) != 0) { + KASSERT(xsavebuf != NULL); + process_write_xsave(l, xsavebuf, xsavelen); + } else if ((flags & _UC_FPU) != 0) { __CTASSERT(sizeof (struct fxsave) == sizeof mcp->__fpregs.__fp_reg_set.__fp_xmm_state); __CTASSERT(sizeof (struct save87) == @@ -1740,7 +1892,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 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 Fri Jul 10 02:39:17 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,27 @@ typedef struct { char __fp_xmm[512]; } __fp_xmm_state; /* x87 and xmm regs in fxsave format */ int __fp_fpregs[128]; + struct { + /* + * `The XSAVE feature set does not use bytes + * 511:416; bytes 463:416 are reserved.' + * + * We take a part out of this to form a pointer + * to an external XSAVE area. This way, we can + * replicate the FXSAVE parts for the benefit + * of userland programs that aren't aware of + * the XSAVE pointer, have used the extended + * CPU registers (ymmN/zmmN/&c.), and want to + * examine the x87/SSE register state in a + * signal handler. The kernel does not use + * this part. + */ + char __fxsave[416]; + char __rsvd[48]; + __greg_t __xsaveptr; + __greg_t __xsavelen; + char __pad[40]; + } __xsave; } __fp_reg_set; int __fp_pad[33]; /* Historic padding */ } __fpregset_t; diff -r 15c667d8eac5 sys/arch/x86/include/cpu_extended_state.h --- a/sys/arch/x86/include/cpu_extended_state.h Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/x86/include/cpu_extended_state.h Fri Jul 10 02:39:17 2026 +0000 @@ -142,6 +142,8 @@ struct xsave_header { }; __CTASSERT(sizeof(struct xsave_header) == 512 + 64); +#define XSAVE_ALIGN 64 + /* * The ymm save area actually follows the xsave_header. */ diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 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 sys/arch/x86/include/specialreg.h --- a/sys/arch/x86/include/specialreg.h Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/x86/include/specialreg.h Fri Jul 10 02:39:17 2026 +0000 @@ -182,16 +182,82 @@ "b\077" "X\0" /* - * Known FPU bits, only these get enabled. The save area is sized for all the - * fields below. + * XCR0_FPU: Known FPU bits, only these get enabled. The save area is + * sized for all the fields below. + * + * Any bits added to this will expand the extended CPU state that we + * may have to save and restore with XSAVE for userland processes, + * either in the kernel when preempting threads, or on the user's stack + * when delivering a signal. + * + * The kernel can dyanmically allocate larger sizes (on amd64, anyway, + * though not currently on i386 or Xen PV). But if the XSAVE area is + * expanded so much that it and mcontext_t exceed MINSIGSTKSZ + * (currently 8192), a userland ABI change and compatibility layer is + * required to accommodate that, because existing programs may use + * sigaltstack(2) with stacks sized for the old MINSIGSTKSZ. + * + * The current stack requirement is 3160 bytes of space plus up to + * 63+15+8=86 bytes of padding for alignment (could be reduced by + * around 512 bytes by having mcontext_t overlap with the XSAVE area a + * little in machdep.c cpu_getmcontext_xsave, but we don't do that + * right now): + * + * - mcontext_t (728 bytes: general registers and 512-byte FXSAVE area) + * - XSAVE header (576 bytes: 512 bytes of FXSAVE, 64 bytes of metadata) + * - AVX state: ymm0..ymm15 high 128-bit halves (256 bytes) + * - AVX-512 state: + * . k0..k7 opmask registers (64 bytes) + * . zmm0..zmm15 high 256-bit halves (512 bytes) + * . zmm16..zmm31 registers (1024 bytes) + * + * Likely future extensions that would expand the state beyond + * MINSIGSTKSZ: + * + * - AMX (Advanced Matrix Extensions) and ACE (AI Compute Extensions) + * state: + * . [AMX/ACE] TILECFG (64 bytes) + * . [AMX/ACE] TILEDATA (8192 bytes) + * . [ACE] SCALEDATA (128 bytes) + * + * As a precaution against ABI breakage, x86/identcpu.c will panic at + * boot if the XSAVE state size enabled in XCR0 exceeds MINSIGSTKSZ. + * + * References: + * + * - Intel 64 and IA-32 Architectures Software Developer's Manual, + * Volume 1: Basic Architecture, Intel, Order Number: 253665-092US, + * June 2026, Sec. 13.1 `XSAVE-Supported Features and State-Component + * Bitmaps', pp. 13-1 -- 13-2. + * https://web.archive.org/web/20260709150417/https://cdrdv2-public.intel.com/922477/253665-092-sdm-vol-1.pdf + * + * - AI Compute Extensions (ACE) Specification, x86 Ecosystem Advisory + * Group, Version 1.15, 2026-05-15, Sec 15.4.1 `XSAVE State + * Components', p. 86. + * https://web.archive.org/web/20260619062626/https://x86ecosystem.org/wp-content/uploads/2026/06/ACE_v1_Specification_public_1_15.pdf */ #if defined __i386__ || defined XENPV /* XXX XENPV PR kern/59371 */ #define XCR0_FPU (XCR0_X87 | XCR0_SSE | XCR0_YMM_Hi128 | \ XCR0_Opmask | XCR0_ZMM_Hi256 | XCR0_Hi16_ZMM) #else #define XCR0_FPU (XCR0_X87 | XCR0_SSE | XCR0_YMM_Hi128 | \ - XCR0_Opmask | XCR0_ZMM_Hi256 | XCR0_Hi16_ZMM | \ - XCR0_TILECFG | XCR0_TILEDATA) + XCR0_Opmask | XCR0_ZMM_Hi256 | XCR0_Hi16_ZMM) +#endif + +/* + * Maximum size of XSAVE state that we can handle without ABI changes + * to userland. Must match usage in cpu_getmcontext. Extra 8 is neeed + * on amd64 to have space for return address in 16-byte-aligned stack + * frame. + */ +#ifdef __x86_64__ +#define XSAVE_MAX_BYTES \ + (MINSIGSTKSZ - (8 + STACK_ALIGNBYTES + \ + sizeof(struct sigframe_siginfo) + (XSAVE_ALIGN - 1))) +#else +#define XSAVE_MAX_BYTES \ + (MINSIGSTKSZ - (STACK_ALIGNBYTES + \ + sizeof(struct sigframe_siginfo) + (XSAVE_ALIGN - 1))) #endif /* diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 2026 +0000 @@ -1039,3 +1039,130 @@ 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. Caller + * must have already checked process_verify_xsavelen, before even + * trying to copyin the content of xsavebuf. + */ +int +process_verify_xsave(struct lwp *l, const struct xsave_header *xsavebuf, + size_t xsavelen) +{ + + KASSERT(process_verify_xsavelen(l, xsavelen) == 0); + + 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, along with any other mcontext + * validation before modifying l's state. + */ +void +process_write_xsave(struct lwp *l, const struct xsave_header *xsavebuf, + size_t xsavelen) +{ + union savefpu *area = fpu_lwp_area(l); + + KASSERT(process_verify_xsave(l, xsavebuf, xsavelen) == 0); + + memcpy(area, xsavebuf, xsavelen); + memset((char *)area + xsavelen, 0, x86_fpu_save_size - xsavelen); +} diff -r 15c667d8eac5 sys/arch/x86/x86/identcpu.c --- a/sys/arch/x86/x86/identcpu.c Thu Jul 09 02:01:07 2026 +0000 +++ b/sys/arch/x86/x86/identcpu.c Fri Jul 10 02:39:17 2026 +0000 @@ -51,6 +51,7 @@ #include #include +#include #include #include @@ -844,9 +845,21 @@ cpu_probe_fpu(struct cpu_info *ci) /* Get component offsets and sizes for the save area */ for (i = XSAVE_YMM_Hi128; i < __arraycount(x86_xsave_offsets); i++) { if (x86_xsave_features & __BIT(i)) { + size_t size; x86_cpuid2(0x0d, i, descs); x86_xsave_offsets[i] = descs[1]; x86_xsave_sizes[i] = descs[0]; + + /* + * Verify the total XSAVE size requires no + * userland ABI change. + */ + size = x86_xsave_offsets[i] + x86_xsave_sizes[i]; + if (size > XSAVE_MAX_BYTES) { + panic("XSAVE size >=%zu" + " exceeds ABI maximum %zu", + size, (size_t)XSAVE_MAX_BYTES); + } } } } diff -r 15c667d8eac5 tests/kernel/t_proccwd.c --- a/tests/kernel/t_proccwd.c Thu Jul 09 02:01:07 2026 +0000 +++ b/tests/kernel/t_proccwd.c Fri Jul 10 02:39:17 2026 +0000 @@ -75,6 +75,8 @@ ATF_TC_BODY(prompt_pid, tc) }; for (i = 0; i < __arraycount(t); i++) { + fprintf(stderr, "try to get cwd of pid %d\n", t[i]); + len = 0; ATF_REQUIRE_EQ(getproccwd(NULL, &len, t[i]), 0); diff -r 15c667d8eac5 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 Fri Jul 10 02:39:17 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