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:
(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.