NetBSD Problem Report #60539

From www@netbsd.org  Tue Aug  4 19:56:46 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits)
	 client-signature RSA-PSS (2048 bits))
	(Client CN "mail.netbsd.org", Issuer "YR2" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 6D13B1A923F
	for <gnats-bugs@gnats.NetBSD.org>; Tue,  4 Aug 2026 19:56:46 +0000 (UTC)
Message-Id: <20260804195645.50A581A9241@mollari.NetBSD.org>
Date: Tue,  4 Aug 2026 19:56:45 +0000 (UTC)
From: nia@sent.at
Reply-To: nia@sent.at
To: gnats-bugs@NetBSD.org
Subject: XSAVE changes break ucontext userspace API
X-Send-Pr-Version: www-1.0
X-From4GNATS: "nia@sent.at via gnats" <gnats-admin@NetBSD.org>

>Number:         60539
>Category:       kern
>Synopsis:       XSAVE changes break ucontext userspace API
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    riastradh
>State:          needs-pullups
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Aug 04 20:00:00 +0000 2026
>Closed-Date:    
>Last-Modified:  Sat Sep 19 15:34:47 +0000 2026
>Originator:     nia
>Release:        NetBSD 11.99.7
>Organization:
The NetBSD __foundation_t
>Environment:
NetBSD siphon 11.99.7 NetBSD 11.99.7 (SIPHON) #0: Sun Aug  2 22:56:47 CEST 2026  nia@siphon:/encrypt/obj/sys/arch/amd64/compile/SIPHON amd64
>Description:
The compiler error:
WasmSignalHandlers.cpp:177:24: error: invalid cast from type
'__fpregset_t' to type 'fxsave*'

The source code in question:
# define XMM_sig(p,i) (((struct fxsave64*)(p)->uc_mcontext.__fpregs)->fx_xmm[i])

This is from an old version of the Mozilla codebase:
https://repo.palemoon.org/MoonchildProductions/UXP/src/commit/4d5b1cc5d8597958e9bbed46b60752395d9d0532/js/src/wasm/WasmSignalHandlers.cpp#L177
>How-To-Repeat:

>Fix:

>Release-Note:

>Audit-Trail:

Responsible-Changed-From-To: lib-bug-people->riastradh
Responsible-Changed-By: riastradh@NetBSD.org
Responsible-Changed-When: Wed, 05 Aug 2026 04:42:56 +0000
Responsible-Changed-Why:
I dug us into this hole, so I guess I'll have to keep digging until
I've dug deep enough to find the law of holes.


From: Taylor R Campbell <riastradh@NetBSD.org>
To: nia@sent.at
Cc: 
Subject: Re: kern/60539 (XSAVE changes break ucontext userspace API)
Date: Wed, 5 Aug 2026 15:36:03 +0000

 So, the good news is:

 1. There is a one-character change that should make this work on all
    versions of NetBSD, for programs that don't use AVX (which wouldn't
    work before, and which it looks like this program doesn't do
    anyway) or don't try to write back registers to be restored on
    signal handler return:

 -# define XMM_sig(p,i) (((struct fxsave64*)(p)->uc_mcontext.__fpregs)->fx_x=
 mm[i])
 +# define XMM_sig(p,i) (((struct fxsave64*)&(p)->uc_mcontext.__fpregs)->fx_=
 xmm[i])

    This change is needed because the mcontext_t member __fpregs
    changed from being an array to being a union:

 	-typedef char __fpregset_t[512] __aligned(8);
 	+typedef union {
 	+	char	__fxsave[512] __aligned(8);
 	+	struct {
 	...
 	+	}	__xsave;
 	+} __fpregset_t;

    And although the change I made to __fpregset_t isn't quite
    API-compatible (sorry), you just can take a pointer to the array
    itself with & (instead taking a pointer to the first element of the
    array, which is what you get without the &) and convert it to a
    pointer to the struct fxsave64 object.  So the change to XMM_sig
    will work on older versions of NetBSD just as well as >=3D11.

 2. Existing binaries that don't use AVX registers are unaffected by
    any ABI consequences of the XSAVE change, and it looks like that
    covers the code you quoted.

 The bad news -- other than the API breakage requiring randomly
 sprinkling ampersands like you're in a fight with the Rust borrow
 checker -- is that if the program _does_ use AVX, then writing back to
 the x87/SSE (st(i), xmmN) registers in the FXSAVE part of the
 ucontext_t will have no effect when the signal handler returns.

 Signal handlers can still _read_ x87/SSE registers out of this
 location -- it's just that _writing_ to them won't change the content
 that gets restored into the registers on signal handler return.

 The reason is that the kernel will lay out two separate copies of the
 FXSAVE area (x87/SSE state), which I did in order to make it easy to
 verify that the XSAVE patch (to save and restore registers _beyond_
 x87/SSE: AVX, AVX-512, AMX, whatever) only has an effect on processes
 that actually require the extra area, when they require it.

 The layout on the stack on signal delivery looks something like this,
 with higher addresses first:

          +-----------------------+ \
          | Hi16_ZMM              |  |
          | --------------------- |  |
          | ZMM_Hi256             |  |
          | --------------------- |  > XSAVE
          | YMM_Hi128             |  |
          | --------------------- |  |
          | FXSAVE in XSAVE       |  |
          +-----------------------+ /
          | (opt. padding bytes)  |
          +-----------------------+ \
          | FXSAVE in ucontext_t  |  |
          | --------------------- |  > ucontext_t
          | rest of ucontext_t    |  |
          +-----------------------+ /
          | siginfo_t             |
          +-----------------------+
          | return address        |
          +-----------------------+ <--- rsp on signal handler entry

 I considered making the XSAVE and ucontext_t areas overlap, like this:

        / +-----------------------+
       |  | Hi16_ZMM              |
       |  | --------------------- |
       |  | ZMM_Hi256             |
 XSAVE <  | --------------------- |
       |  | YMM_Hi128             |
       |  *-----------------------* \
       |  | FXSAVE in ucontext_t  |  |
        \ *-----------------------*  > ucontext_t
          | rest of ucontext_t    |  |
          +-----------------------+ /
          | siginfo_t             |
          +-----------------------+
          | return address        |
          +-----------------------+ <--- rsp on signal handler entry

 However, when the signal handler returns to the return address we
 provided, it enters the signal trampoline, which calls setcontext(2):

      72 NENTRY(__sigtramp_siginfo_2)
      73 	movq	%r15,%rdi
      74 	movq	$SYS_setcontext, %rax
      75 	syscall

 https://nxr.netbsd.org/xref/src/lib/libc/arch/x86_64/sys/__sigtramp2.S?r=3D=
 1.9

 And the first thing setcontext(2) does is copyin sizeof(ucontext_t)
 bytes -- that is, _only_ the ucontext_t, _not including_ the rest of
 the XSAVE area, because at this point the machine-independent
 setcontext(2) logic has no way to know that the downstream
 machine-dependent cpu_setmcontext logic is hungry for more bytes:

     341 int
     342 sys_setcontext(struct lwp *l, const struct sys_setcontext_args *uap,
     343     register_t *retval)
     344 {
     345 	/* {
     346 		syscallarg(const ucontext_t *) ucp;
     347 	} */
     348 	struct proc *p =3D l->l_proc;
     349 	ucontext_t uc;
     350 	int error;
     351=20
     352 	error =3D copyin(SCARG(uap, ucp), &uc, sizeof (uc));
 ...
     358 	error =3D setucontext(l, &uc);

 https://nxr.netbsd.org/xref/src/sys/kern/sys_sig.c?r=3D1.62#341

 And by the time control reaches cpu_setmcontext, it doesn't even know
 where it came from in userland, so it doesn't know where to copyin the
 rest of the XSAVE area from.  I could in principle have added more MD
 hooks to sys_setcontext to tell conditionally copy in larger chunks
 but that seemed like a very risky change to pull up to 11 in order to
 fix a long-term embarrassing x86-only bug.

 Instead, I just put the XSAVE area in a separate space on the stack
 (again, to keep the stack manipulation logic in sendsig_siginfo very
 easy to audit), and carved out 16 bytes of the 512-byte FXSAVE area
 that are architecturally unused (bytes 511:416 are not `reserved' like
 bytes 463:416 but `not use[d]' by the architecture according to the
 Intel manual, and `Reserved, IGN' rather than (e.g.) `Reserved, MBZ'
 according to the AMD manual), to store a pointer to and length of the
 XSAVE area.

 That way, the x86 cpu_setmcontext can find it to copy it in (after
 validating the length) in order to restore the state:

    2264 	if ((flags & _UC_XSAVE) !=3D 0) {
    2265 		const struct xsave_header *user_xsave =3D
    2266 		    (void *)(uintptr_t)mcp->__fpregs.__xsave.__xsaveptr;
    2267=20
    2268 		xsavelen =3D mcp->__fpregs.__xsave.__xsavelen;
    2269 		error =3D process_verify_xsavelen(l, xsavelen);
    2270 		if (error !=3D 0)
    2271 			goto out;
    2272 		xsavebuf =3D kmem_alloc(xsavelen, KM_SLEEP);
    2273 		error =3D copyin(user_xsave, xsavebuf, xsavelen);
    2274 		if (error !=3D 0)
    2275 			goto out;
    2276 		error =3D process_verify_xsave(l, xsavebuf, xsavelen);
    2277 		if (error !=3D 0)
    2278 			goto out;
    2279 	}

 https://nxr.netbsd.org/xref/src/sys/arch/amd64/amd64/machdep.c?r=3D1.380#22=
 55

 (That's why I changed the mcontext_t __fpregs member to be a union: so
 I could add named __xsaveptr/__xsavelen members.  I don't know why it
 was ever an array, anyway; that didn't facilitate access to the CPU
 state the way that, say, a struct with st0/st1/.../st7/xmm0/xmm1/...
 members would have.)

 So, if you want access to state beyond x87/SSE -- the high 128-bit
 halves of the AVX/AVX2 YMM registers, the high 256-bit halves of the
 AVX512 ZMM registers or the high 16 ZMM registers, the obsolete BND
 registers, the future AMX/ACE tile data, &c. -- you will need to fish
 it out of uc->uc_mcontext.__fpregs.__xsaveptr/__xsavelen.  And
 avoiding the overlap meant the new logic in amd64/machdep.c -- and
 amd64/netbsd32_machdep.c and i386/machdep.c -- would be nice and
 simple and very clearly conditional on `if (process_needs_xsave_p())'.

 That meant, however, that there would be two locations where the
 FXSAVE area could live: one inside the ucontext_t, and one in the
 separate XSAVE area.

 `Not to worry,' I thought to myself, `because we can just keep copying
 out the ucontext_t FXSAVE area so existing debuggers can still read
 the state even if they don't know about the separate XSAVE area, and
 it'll all be hunky-dory, right?'

 Wrong!  FPU trap handlers (and debuggers) can substitute values in
 computations, so they may also need to write back registers.  And with
 two FXSAVE areas, which one does the kernel restore from?  Well, I
 picked the XSAVE one, not the ucontext_t one, again to keep it simple.

 So, perhaps we should combine the approaches:

 1. use the __xsaveptr/__xsavelen so that the MD cpu_setmcontext knows
    where to find the XSAVE area,
 AND
 2. make the XSAVE area overlap the ucontext_t so the FXSAVE sections
    of the two coincide, with some careful attention to stack pointer
    arithmetic and alignment in sendsig_siginfo.

 This way:

 - Programs that hordes of engineers around the world have rushed to
   adapt to NetBSD 11.0 by using __xsaveptr/__xsavelen will still work
   in NetBSD 11.1 and NetBSD 12, i.e., we don't break compatibility
   _again_ in quick succession.

 - Programs whose inexcusibly lazy maintainers are way behind the times
   and STILL TODAY haven't done a thing to adapt to NetBSD 11.0 will
   also continue to work even if they newly use AVX when they didn't
   before, i.e., we restore `compatibility' for programs that never
   worked right in the first place on NetBSD.

   And for NetBSD>11.0, they will be able to just cast __fpregs to an
   XSAVE area for access to >=3DAVX state, without having to go through
   the __xsaveptr/__xsavelen business altogether.

 - Programs that don't use extended CPU state beyond x87/SSE remain
   unaffected.

 - We can pull up the changes -- with the array-to-union change under
   #ifdef _KERNEL -- to netbsd-10 and netbsd-9 to make AVX register
   access work reliably in the face of signal handlers without breaking
   the API or affecting `compatibility' of programs using AVX registers
   that never worked right in the first place on NetBSD.

From: Taylor R Campbell <riastradh@NetBSD.org>
To: nia@sent.at, gnats-bugs@netbsd.org, lib-bug-people@netbsd.org,
	netbsd-bugs@netbsd.org, gnats-admin@netbsd.org
Cc: 
Subject: Re: kern/60539 (XSAVE changes break ucontext userspace API)
Date: Thu, 6 Aug 2026 02:47:23 +0000

 > Date: Wed, 5 Aug 2026 15:36:03 +0000
 > From: Taylor R Campbell <riastradh@NetBSD.org>
 > 
 > I considered making the XSAVE and ucontext_t areas overlap, like this:
 > 
 >        / +-----------------------+
 >       |  | Hi16_ZMM              |
 >       |  | --------------------- |
 >       |  | ZMM_Hi256             |
 > XSAVE <  | --------------------- |
 >       |  | YMM_Hi128             |
 >       |  *-----------------------* \
 >       |  | FXSAVE in ucontext_t  |  |
 >        \ *-----------------------*  > ucontext_t
 >          | rest of ucontext_t    |  |
 >          +-----------------------+ /
 >          | siginfo_t             |
 >          +-----------------------+
 >          | return address        |
 >          +-----------------------+ <--- rsp on signal handler entry
 > 
 > However, [stuff about setcontext(2) copyin size issues]
 > [...]
 > So, perhaps we should combine the approaches:
 > 
 > 1. use the __xsaveptr/__xsavelen so that the MD cpu_setmcontext knows
 >    where to find the XSAVE area,
 > AND
 > 2. make the XSAVE area overlap the ucontext_t so the FXSAVE sections
 >    of the two coincide, with some careful attention to stack pointer
 >    arithmetic and alignment in sendsig_siginfo.

 Unfortunately, I recall now another reason why I didn't do it this
 way.  Although it looks like this might work on amd64 (and perhaps we
 should do it on amd64), it won't work at all on i386 because on i386,
 ucontext_t (and more specifically mcontext_t) doesn't end with the
 FXSAVE area -- the thread-local storage base pointer comes after it:

     115 typedef struct {
     116 	__gregset_t	__gregs;
     117 	__fpregset_t	__fpregs;
 ==> 118 	__greg_t	_mc_tlsbase;
     119 } mcontext_t;

 https://nxr.netbsd.org/xref/src/sys/arch/i386/include/mcontext.h?r=1.20#115

 i386 does have 33*4=132 bytes of unused padding at the end of
 __fpregset_t, but that's not enough for even the high 128-bit halves
 of the YMM registers (256 bytes), which is the minimal additional
 state for AVX to work:

      80 typedef struct {
      81 	union {
 ...
     110 	} __fp_reg_set;
     111 	int 	__fp_pad[33];			/* Historic padding */
     112 } __fpregset_t;
     113 __CTASSERT(sizeof (__fpregset_t) == 512 + 33 * 4);

 So, short of versioning setcontext (and sigaction and everything else
 that touches this) to provide an alternate mcontext_t that doesn't
 match the SysV ABI, I don't think there's a way to provide a single
 contiguous XSAVE area that overlaps the FXSAVE area, and thus we
 either need to:

 (a) _not_ have a contiguous XSAVE area, and instead assemble an XSAVE
     area from the existing mcontext_t FXSAVE area and a pointer to the
     rest (which might be fine except we already shipped 11.0 to
     provide a contiguous XSAVE area),
 or
 (b) have two FXSAVE areas (one in ucontext_t, one elsewhere) and make
     a decision about which one is treated as canonical (which is where
     we are now, and the one in the XSAVE area is treated as canonical
     in 11.0, so it might be a bit late to change that).


 Now, this all only affects existing applications that:

 1. already wanted to write to the x87/SSE registers in a signal
    handler to affect the CPU state on return from signal,
 AND
 2. still want to use the same unmodified logic when the signal handler
    might interrupt code that uses _additional_ CPU state.

 I think such applications are likely to be inherently problematic.  If
 you're doing value substitution in an FPU trap handler, and some
 AVX512 instruction operating on a 512-bit ZMM vector register traps,
 does it make sense for your trap handler to try to replace _just the
 low 128 bits_ of the ZMM register (i.e., the 128-bit XMM register it
 aliases) and restart the instruction stream?  I think this is not very
 likely.

 But I'm open to other thoughts on the matter.

From: Robert Elz <kre@munnari.OZ.AU>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@netbsd.org, lib-bug-people@netbsd.org, netbsd-bugs@netbsd.org
Subject: Re: kern/60539 (XSAVE changes break ucontext userspace API)
Date: Thu, 06 Aug 2026 18:10:09 +0700

     Date:        Thu, 6 Aug 2026 02:47:23 +0000
     From:        Taylor R Campbell <riastradh@NetBSD.org>
     Message-ID:  <20260806024725.4385484D7C@mail.netbsd.org>

 You can laugh as much as you like, and no need to reply, I know how
 absurd this would be ...

   | I think such applications are likely to be inherently problematic.  If
   | you're doing value substitution in an FPU trap handler, and some
   | AVX512 instruction operating on a 512-bit ZMM vector register traps,

 And if you were doing software emulation of the AVX512 instructions,
 wouldn't the trap handler need to be able to access the (emulated)
 registers ?

 kre

From: Taylor R Campbell <riastradh@NetBSD.org>
To: Robert Elz <kre@munnari.OZ.AU>
Cc: gnats-bugs@netbsd.org, lib-bug-people@netbsd.org, netbsd-bugs@netbsd.org
Subject: Re: kern/60539 (XSAVE changes break ucontext userspace API)
Date: Thu, 6 Aug 2026 14:41:22 +0000

 > Date: Thu, 06 Aug 2026 18:10:09 +0700
 > From: Robert Elz <kre@munnari.OZ.AU>
 > 
 >     Date:        Thu, 6 Aug 2026 02:47:23 +0000
 >     From:        Taylor R Campbell <riastradh@NetBSD.org>
 >     Message-ID:  <20260806024725.4385484D7C@mail.netbsd.org>
 > 
 >   | I think such applications are likely to be inherently problematic.  If
 >   | you're doing value substitution in an FPU trap handler, and some
 >   | AVX512 instruction operating on a 512-bit ZMM vector register traps,
 > 
 > And if you were doing software emulation of the AVX512 instructions,
 > wouldn't the trap handler need to be able to access the (emulated)
 > registers ?

 Yes, in that case you would absolutely need to know about the new
 XSAVE pointer in mcontext_t:

 static void
 handle_sigfpe(int signo, siginfo_t *si, void *ctx)
 {
 	ucontext_t *uc = ctx;
 	mcontext_t *mc = &uc->uc_mcontext;
 	struct xsave_header *xsh =
 	    (struct xsave_header *)mc->__fpregs.__xsaveptr;
 	size_t xslen = (size_t)mc->__fpregs.__xsavelen;

 	... if (xs->xsh_xstate_bv & XCR0_YMM_Hi128) ...
 }

 The hypothetical I was considering is a handler previously written for
 x87/SSE that, say, replaces all double-precision subnormal inputs and
 results by zero:

 static void
 handle_sigfpe(int signo, siginfo_t *si, void *ctx)
 {
 	ucontext_t *uc = ctx;
 	mcontext_t *mc = &uc->uc_mcontext;
 	struct fxsave *fx = (struct fxsave *)&mc->__fpregs.__fxsave;

 	for (i = 0; i < 16; i++) {
 		if (issubnormal(fx->fx_xmm[i].dbl[0]))
 			fx->fx_xmm[i].dbl[0] = 0;
 		if (issubnormal(fx->fx_xmm[i].dbl[1]))
 			fx->fx_xmm[i].dbl[1] = 0;
 	}
 	/* ditto x87 registers */
 }

 _Reading_ the FXSAVE state will continue to work, but in a process
 that uses AVX instructions -- at least, if the signal is delivered any
 time between the first AVX instruction and VZEROUPPER -- _writing_
 back changes to the FXSAVE state will be dropped.

 But also -- aside from the obvious problems this signal handler has
 like not catching any results that have already been written to memory
 and not making sense when the registers are used for anything other
 than a pair of doubles quantities -- this signal handler is unaware of
 the upper parts of the AVX/AVX512 registers ymmN/zmmN whose lower 128
 bits alias xmmN, so it will only convert the low 128 bits of each one.

 So I'm struggling to imagine a plausible scenario where the change
 disabling writeback of the ucontext_t FXSAVE area in the presence of
 AVX instructions will actually break something that isn't already
 profoundly broken.

 In other words, I think it wasn't necessary of me to expand the scope
 of the reported PR from `x86 mcontext.h API broke requiring you to
 insert a single & in existing programs' (so if we pull up the XSAVE
 changes to 9 and 10, the mcontext.h part should perhaps be under
 #ifdef _KERNEL) to `ABI change could have broken existing compiled
 programs' because the only ones it could have broken must have already
 been broken.

From: Taylor R Campbell <riastradh@NetBSD.org>
To: nia@sent.at, gnats-bugs@netbsd.org, lib-bug-people@netbsd.org,
	netbsd-bugs@netbsd.org, gnats-admin@netbsd.org
Cc: 
Subject: Re: kern/60539 (XSAVE changes break ucontext userspace API)
Date: Thu, 6 Aug 2026 20:17:31 +0000

 This is a multi-part message in MIME format.
 --=_LF0R1FFWgpNWTy7tDsnl9sFJlFnD3FDt
 Content-Transfer-Encoding: quoted-printable

 > Date: Wed, 5 Aug 2026 15:36:03 +0000
 > From: Taylor R Campbell <riastradh@NetBSD.org>
 >=20
 > So, the good news is:
 >=20
 > 1. There is a one-character change that should make this work on all
 >    versions of NetBSD, for programs that don't use AVX (which wouldn't
 >    work before, and which it looks like this program doesn't do
 >    anyway) or don't try to write back registers to be restored on
 >    signal handler return:
 >=20
 > -# define XMM_sig(p,i) (((struct fxsave64*)(p)->uc_mcontext.__fpregs)->fx=
 _xmm[i])
 > +# define XMM_sig(p,i) (((struct fxsave64*)&(p)->uc_mcontext.__fpregs)->f=
 x_xmm[i])

 I should add, for completeness, that if you do want to write the
 registers back in signal handlers in programs that use AVX
 instructions, you'll have to check _UC_XSAVE to see whether there's a
 possibly separate XSAVE area:

 # define XMM_sig(p,i) \
     (((struct fxsave64 *)((p)->uc_flags & _UC_XSAVE \
       ? (void *)(p)->uc_mcontext.__fpregs.__xsave.__xsaveptr \
       : &(p)->uc_mcontext.__fpregs))->fx_xmm[i])

 The member ucontext_t::uc_mcontext.__fpregs.__xsave.__xsaveptr is the
 address of the start of an XSAVE area having
 ucontext_t::uc_mcontext.__fpregs.__xsave.__xsavelen bytes, in the
 OS-independent x86 XSAVE format (possibly XSAVE, possibly XSAVEOPT,
 possibly XSAVEC -- it's the user's responsibility to figure that out
 from the XSAVE header's XSTATE_BV/XCOMP_BV bits).


 I thought of a scenario where this could (in principle) cause trouble,
 but I still think it's rather unlikely:

 1. Some micro-optimized library provides optional AVX implementations
    of subroutines like memcpy, ChaCha encryption, or whatever, based
    on runtime CPUID and XCR0 detection.

 2. Despite being intended for micro-optimization, this library AVX
    code _does not_ do VZEROUPPER when done, so the AVX registers
    remain in use indefinitely, and hence NetBSD thinks the thread
    requires XSAVE and not just FXSAVE.

 3. The rest of the program carefully installs a SIGFPE handler _only_
    while it is running known-safe x87/SSE instruction sequences, such
    as those generated by a JIT compiler.

 Parts (1) and (3) are likely to happen.  But part (2) strikes me as
 unlikely, because the whole point of this is micro-optimization, and
 if you forget to do VZEROUPPER, that incurs a performance penalty on
 all non-AVX SSE instructions.

 I'm still open to thoughts on:

 - Are there applications that the XSAVE ABI change really breaks?

 - Should we try to dig ourselves deeper^W^W^Wfind a way to handle
   mcontext_t on signal delivery differently?


 The attached (untested) patch makes the XSAVE area overlap the
 mcontext_t so there's only one FXSAVE area, on amd64.  But it can't be
 done on i386 because it would conlict with mcontext_t::_mc_tlsbase.
 So I'm on the fence about whether to bother at all.  Mostly the
 benefit is probably just reducing the copyouts on signal delivery by
 512 bytes.

 --=_LF0R1FFWgpNWTy7tDsnl9sFJlFnD3FDt
 Content-Type: text/plain; charset="ISO-8859-1"; name="pr60539-xsavemcontextcompat"
 Content-Transfer-Encoding: quoted-printable
 Content-Disposition: attachment; filename="pr60539-xsavemcontextcompat.patch"

 # HG changeset patch
 # User Taylor R Campbell <riastradh@NetBSD.org>
 # Date 1786044700 0
 #      Thu Aug 06 19:31:40 2026 +0000
 # Branch trunk
 # Node ID 54069feb2f11ce0b39191566ccb7956862e5c21f
 # Parent  b125bad3265e13dfd5a772b3676744fca85ce488
 # EXP-Topic riastradh-pr60539-xsavemcontextcompat
 WIP: amd64: Make XSAVE area overlap mcontext_t on signal delivery.

 This way:

 1. We copy out 512 bytes fewer.

 2. There is only one place where the x87/SSE registers are written to
    -- and, more importantly, where they might be read from on return
    from signal.

 3. Existing applications that write to the mcontext_t's FXSAVE area
    to change x87/SSE register content in threads that are using
    extended CPU state like the AVX registers will still work (though
    I suspect there are few, if any, such applications).

 4. Newly adjusted applications that use 11.0's XSAVE area pointer
    embedded in a padding cabinet in disused lavatory with a sign on
    it saying `BEWARE OF LEOPARD --Intel' in the mcontext_t's FXSAVE
    area will still work.

 Unfortunately, we can't do the same for i386, because mcontext_t has
 extra stuff in it after the FXSAVE area:

     115 typedef struct {
     116         __gregset_t     __gregs;
     117         __fpregset_t    __fpregs;
     118         __greg_t        _mc_tlsbase;
     119 } mcontext_t;

 https://nxr.netbsd.org/xref/src/sys/arch/i386/include/mcontext.h?r=3D1.20#1=
 15

 PR kern/60539: XSAVE changes break ucontext userspace API

 diff -r b125bad3265e -r 54069feb2f11 sys/arch/amd64/amd64/machdep.c
 --- a/sys/arch/amd64/amd64/machdep.c	Mon Aug 03 19:24:48 2026 +0000
 +++ b/sys/arch/amd64/amd64/machdep.c	Thu Aug 06 19:31:40 2026 +0000
 @@ -635,14 +635,23 @@ sendsig_siginfo(const ksiginfo_t *ksi, c
  	 * FXSAVE area.
  	 */
  	if (process_xsave_needed_p(l)) {
 +		enum {
 +			overlap =3D (sizeof(struct sigframe_siginfo) -
 +			    offsetof(struct sigframe_siginfo,
 +				sf_uc.uc_mcontext.__fpregs)),
 +		};
 +
  		process_read_xsave(l, &xsavebuf, &xsavelen);
  		KASSERT(xsavebuf !=3D NULL);
  		KASSERT(xsavelen <=3D XSAVE_MAX_BYTES);
 +		CTASSERT(overlap <=3D XSAVE_MAX_BYTES);
 =20
  		KASSERT(!onstack || sp >=3D (char *)l->l_sigstk.ss_sp);
  		KASSERT(!onstack ||
  		    sp - (char *)l->l_sigstk.ss_sp >=3D xsavelen);
 -		sp -=3D xsavelen;
 +		KASSERT(!onstack ||
 +		    sp - (char *)l->l_sigstk.ss_sp >=3D overlap);
 +		sp -=3D MAX(xsavelen, overlap);
 =20
  		KASSERT(!onstack || sp >=3D (char *)l->l_sigstk.ss_sp);
  		KASSERT(!onstack ||
 @@ -652,6 +661,10 @@ sendsig_siginfo(const ksiginfo_t *ksi, c
  		KASSERT(!onstack || sp >=3D (char *)l->l_sigstk.ss_sp);
  		KASSERT(((uintptr_t)sp & (XSAVE_ALIGN - 1)) =3D=3D 0);
  		user_xsave =3D (void *)sp;
 +
 +		CTASSERT((overlap % XSAVE_ALIGN) =3D=3D 0);
 +		CTASSERT((overlap & STACK_ALIGNBYTES) =3D=3D 0);
 +		sp +=3D overlap;
  	}
 =20
  	/*
 @@ -672,6 +685,17 @@ sendsig_siginfo(const ksiginfo_t *ksi, c
  	KASSERT(!onstack || (char *)fp >=3D (char *)l->l_sigstk.ss_sp);
  	KASSERT(((uintptr_t)fp & STACK_ALIGNBYTES) =3D=3D 8);
 =20
 +	/*
 +	 * If we have to use XSAVE, the FXSAVE area of the ucontext_t
 +	 * on the user's stack must line up with the FXSAVE subarea of
 +	 * the XSAVE area on the user's stack.
 +	 */
 +	KASSERT(xsavebuf =3D=3D 0 ||
 +	    (((uintptr_t)&fp->sf_uc.uc_mcontext.__fpregs & (XSAVE_ALIGN - 1))
 +		=3D=3D 0));
 +	KASSERT(xsavebuf =3D=3D 0 || (uintptr_t)user_xsave =3D=3D
 +	    (uintptr_t)&fp->sf_uc.uc_mcontext.__fpregs);
 +
  	memset(&frame, 0, sizeof(frame));
  	frame.sf_ra =3D (uint64_t)ps->sa_sigdesc[sig].sd_tramp;
  	frame.sf_si._info =3D ksi->ksi_info;
 @@ -686,10 +710,12 @@ sendsig_siginfo(const ksiginfo_t *ksi, c
  	cpu_getmcontext(l, &frame.sf_uc.uc_mcontext, &frame.sf_uc.uc_flags);
 =20
  	/*
 -	 * If we have to use XSAVE, copy out that area separately --
 -	 * and be ready to bail if it failed.
 +	 * If we have to use XSAVE, copy out the part of it past the
 +	 * FXSAVE area separately -- and be ready to bail if it failed.
  	 */
  	if (xsavebuf) {
 +		KASSERT((void *)&fp->sf_uc.uc_mcontext.__fpregs =3D=3D
 +		    (void *)user_xsave);
  		error =3D cpu_getmcontext_xsave(l, &frame.sf_uc.uc_mcontext,
  		    &frame.sf_uc.uc_flags, xsavebuf, xsavelen, user_xsave);
  		if (error !=3D 0)
 @@ -2200,9 +2226,15 @@ cpu_getmcontext(struct lwp *l, mcontext_
  /*
   * 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.
 + *	Copy out xsavebuf[512..xsavelen) to user_xsave[512..xsavelen),
 + *	set mcp to point at it, and set _UC_XSAVE in *flags.  Caller:
 + *
 + *	- must have already initialized the FXSAVE area of mcontext_t,
 + *	- must have already set _UC_FPU in *flags,
 + *	- must have arranged user_xsave[0..512) to overlap with the
 + *	  FXSAVE area of mcontext_t, and
 + *	- must subsequently copy out the mcontext_t updated with a
 + *	  pointer/length to the XSAVE area.
   *
   *	May fail if the copyout fails.
   */
 @@ -2211,14 +2243,20 @@ cpu_getmcontext_xsave(struct lwp *l, mco
      const struct xsave_header *xsavebuf, size_t xsavelen,
      struct xsave_header *user_xsave)
  {
 +	enum { fxsavelen =3D sizeof(mcp->__fpregs.__fxsave) };
  	int error;
 =20
 +	CTASSERT(fxsavelen =3D=3D 512);
 +
  	KASSERT(*flags & _UC_FPU);
 +	KASSERT(fxsavelen <=3D xsavelen);
 +	KDASSERT(memcmp(&mcp->__fpregs.__fxsave, xsavebuf, fxsavelen) =3D=3D 0);
 =20
  	/*
 -	 * Copy out the XSAVE area.
 +	 * Copy out the part of the XSAVE area that doesn't overlap.
  	 */
 -	error =3D copyout(xsavebuf, user_xsave, xsavelen);
 +	error =3D copyout((const char *)xsavebuf + fxsavelen,
 +	    (char *)user_xsave + fxsavelen, xsavelen - fxsavelen);
  	if (error !=3D 0)
  		return error;
 =20

 --=_LF0R1FFWgpNWTy7tDsnl9sFJlFnD3FDt--

From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/60539 CVS commit: src/sys/arch/amd64/amd64
Date: Sat, 19 Sep 2026 15:18:30 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Sat Sep 19 15:18:30 UTC 2026

 Modified Files:
 	src/sys/arch/amd64/amd64: machdep.c

 Log Message:
 amd64: Make XSAVE area overlap mcontext_t on signal delivery.

 Additionally, if the stack pointer is already such that pushing a
 struct sigframe_siginfo would make it congruent to -8 modulo the
 stack alignment, as the signal handler expects (-8 for the return
 address on an aligned stack frame), don't re-align it again -- it's
 just fine as is.

 Previously, for NetBSD 11.0, we would copy out an XSAVE area
 separately from ucontext_t, and copy out the FXSAVE content twice in
 two different places on the stack -- one in the ucontext_t for
 compatibility so older applications can still read x87/SSE registers,
 and one in the XSAVE area:

           +-----------------------+ \
           | (other XSAVE stuff)   |  |
           | --------------------- |  |
           | Hi16_ZMM              |  |
           | --------------------- |  |
           | ZMM_Hi256             |  > XSAVE
           | --------------------- |  |
           | YMM_Hi128             |  |
           | --------------------- |  |
           | FXSAVE in XSAVE       |  |
           +-----------------------+ /
           | (opt. padding bytes)  |
           +-----------------------+ \
           | FXSAVE in ucontext_t  |  |
           | --------------------- |  > ucontext_t
           | rest of ucontext_t    |  |
           +-----------------------+ /
           | siginfo_t             |
           +-----------------------+
           | return address        |
           +-----------------------+ <--- rsp on signal handler entry

 But the 11.0 layout could, in theory, break older applications with
 signal handlers that _write_ the x87/SSE registers -- even if they
 use VZEROUPPER or VZEROALL as is customary -- if some other part of
 the application (e.g., an optimized AVX512 crypto routine in OpenSSL)
 has _ever_ used the high 16 ZMM registers, which _isn't_ customarily
 followed by VZEROUPPER/VZEROALL.

 With this change, we move the XSAVE area so that its FXSAVE part
 overlaps with the ucontext_t, obviating the need to copy out the
 FXSAVE content twice, and restoring compatibility for x87/SSE
 register writeback in signal handlers:

         / +-----------------------+
        |  | (other XSAVE stuff)   |
        |  | --------------------- |
        |  | Hi16_ZMM              |
        |  | --------------------- |
  XSAVE <  | ZMM_Hi256             |
        |  | --------------------- |
        |  | YMM_Hi128             |
        |  *-----------------------* \
        |  | FXSAVE in ucontext_t  |  |
         \ *-----------------------*  > ucontext_t
           | rest of ucontext_t    |  |
           +-----------------------+ /
           | siginfo_t             |
           +-----------------------+ =0 (mod 16)
           | return address        |
           +-----------------------+ <--- rsp on signal handler entry

 This way:

 1. We copy out 512 bytes fewer.

 2. There is only one place where the x87/SSE registers are written to
    -- and, more importantly, where they might be read from on return
    from signal.

 3. Existing applications that write to the mcontext_t's FXSAVE area
    to change x87/SSE register content in threads that are using
    extended CPU state like the AVX registers will still work.  This
    may happen even if use of AVX registers is always ended by
    VZEROUPPER or VZEROALL, if an AVX-512 subroutine ever uses the
    high 16 ZMM registers -- those are _not_ zeroed by VZEROUPPER or
    VZEROALL, and remain persistently in the XSAVE state.

 4. Newly adjusted applications that use 11.0's XSAVE area pointer
    embedded in a padding cabinet in disused lavatory with a sign on
    it saying `BEWARE OF LEOPARD --Intel' in the mcontext_t's FXSAVE
    area will still work.

 Unfortunately, we can't do the same for i386, because mcontext_t has
 extra stuff in it after the FXSAVE area:

     115 typedef struct {
     116         __gregset_t     __gregs;
     117         __fpregset_t    __fpregs;
     118         __greg_t        _mc_tlsbase;
     119 } mcontext_t;

 https://nxr.netbsd.org/xref/src/sys/arch/i386/include/mcontext.h?r=1.20#115

 Fortunately, the high 16 ZMM registers do not exist in 32-bit mode!
 In fact there are only eight ZMM registers.  So as long as
 application making use of the high 128-bit halves of the YMM
 registers or the high 256-bit halves of the ZMM registers use
 VZEROUPPER/VZEROALL when they're done -- as is customary in the ABI
 -- any signal handlers that read and write x87/SSE state, in sections
 of code using only x87/SSE state, were not broken in 11.0 with the
 separate copies of FXSAVE content in the split XSAVE and ucontext_t
 areas.

 PR kern/60539: XSAVE changes break ucontext userspace API


 To generate a diff of this commit:
 cvs rdiff -u -r1.380 -r1.381 src/sys/arch/amd64/amd64/machdep.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

State-Changed-From-To: open->needs-pullups
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Sat, 19 Sep 2026 15:34:47 +0000
State-Changed-Why:
obscure ABI issue for amd64 fixed in HEAD, needs pullup-11, inapplicable<11

API issue has backward-compatible workaround (insert &), probably not
worthwhile to do any more API shenanigans since 11.0 is already out and
that's what packages are built against.


>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.