Uninitialized struct sigaction trailing padding leaked to userspace via oact copyout
| Field | Value |
|---|---|
| ID | DF-0007 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-908 Use of Uninitialized Resource |
| File | sys/kern/kern_sig.c |
| Lines | 384, 260-279, 397 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
On 64-bit (amd64) struct sigaction is 32 bytes with 4 bytes of trailing
alignment padding at offset 28-31. sys_sigaction() stack-allocates its
oact uninitialized; kern_sigaction() fills oact field-by-field (handler,
mask, flags) but never writes the trailing padding; copyout(oactp, uap->oact,
sizeof(oact)) then copies the full 32-byte struct to userspace โ leaking up to
4 bytes of kernel-stack residue to an unprivileged caller of
sigaction(signo, NULL, &oact). This is a classic CWE-908 info-leak (a weak
KASLR/stack-residue oracle). i386 is unaffected (sizeof(struct sigaction)
== 24, no trailing pad).
Root cause
struct sigaction (sys/sys/signal.h:221-228) on amd64:
offset 0: union __sigaction_u { void (*)(int); void (*)(int,siginfo*,void*); } (8 bytes)
offset 8: int sa_flags (4 bytes)
offset 12: sigset_t sa_mask /* unsigned int __bits[_SIG_WORDS=4] */ (16 bytes, 4-byte aligned)
offset 28: <trailing alignment padding> (4 bytes) <-- NOT a named field
sizeof(struct sigaction) == 32 (struct alignment is 8)
sigset_t is unsigned int __bits[4] (sys/sys/_sigset.h:35-39, 16 bytes,
4-byte-aligned), so sa_mask sits directly after sa_flags with no internal
gap; the only uninitialized region is the 4 trailing padding bytes at
offset 28-31 that pad the struct to 8-byte alignment.
In sys_sigaction (sys/kern/kern_sig.c:384):
struct sigaction act, oact; /* uninitialized stack variables */
kern_sigaction (sys/kern/kern_sig.c:260-279) writes only the named
fields:
if (oact) {
oact->sa_handler = ps->ps_sigact[_SIG_IDX(sig)]; /* offset 0-7 */
oact->sa_mask = ps->ps_catchmask[_SIG_IDX(sig)];/* offset 12-27 */
oact->sa_flags = 0; /* offset 8-11 */
/* ... |= SA_* ... into sa_flags ... */
}
โ it never touches offset 28-31. Then sys_sigaction does
(sys/kern/kern_sig.c:397):
error = copyout(oactp, uap->oact, sizeof(oact)); /* copies all 32 bytes */
which propagates the 4 uninitialized trailing bytes to userspace. On i386
the union is 4 bytes and sizeof(struct sigaction) == 24 (struct alignment 4),
so there is no trailing padding and the path is unaffected.
Threat model & preconditions
- Attacker position: any local unprivileged user.
- Privileges gained or impact: information disclosure. Each
sigaction(signo, NULL, &oact)call leaks up to 4 bytes of kernel stack whose contents depend on prior syscall residue (pointer fragments, etc.). Not a direct LPE, but a samplable KASLR/stack-residue oracle. - Required config or capabilities: none; default amd64 kernel.
- Reachability: the
sigaction(2)syscall, directly.
Proof of concept
PoC source: findings/poc/DF-0007/leak_sigaction.c
Prefills a userland struct sigaction with a marker (0xAA), calls
sigaction(SIGUSR1, NULL, &oact), and inspects the trailing 4 bytes
(offset 28-31).
Build & run
cc -o leak_sigaction findings/poc/DF-0007/leak_sigaction.c ./leak_sigaction # as a non-root user, on amd64
Expected output
sizeof(struct sigaction) = 32 sample 0: padding bytes = 78 56 34 12 (word 0x12345678) ... samples with non-marker padding (leaked residue): N/8 result: LEAK CONFIRMED
The bytes vary (kernel-stack residue); what matters is they are neither the marker nor data the kernel wrote. On a fixed kernel the padding reads as the marker or zero.
Impact
Low-impact kernel-memory info leak (up to 4 bytes per call, stack residue). Valuable primarily as a hardening fix and as one ingredient in a larger info-leak/KASLR-defeat toolkit. Rated Info.
Recommended fix
Zero the stack-allocated oact (and act) before use, so the padding is
defined:
--- a/sys/kern/kern_sig.c
+++ b/sys/kern/kern_sig.c
@@ -381,7 +381,8 @@ int
sys_sigaction(struct sysmsg *sysmsg, const struct sigaction_args *uap)
{
- struct sigaction act, oact;
+ struct sigaction act = {};
+ struct sigaction oact = {};
struct sigaction *actp, *oactp;
int error;
A defense-in-depth alternative is to zero the trailing padding explicitly in
kern_sigaction before returning, but initializing the whole struct at the
syscall boundary is the minimal, idiomatic fix. (Equivalent discipline should
be applied to other field-by-field copyout paths in the file as they are
encountered.)
References
sys/kern/kern_sig.c:384โ uninitializedoactdeclaration.sys/kern/kern_sig.c:260-279โ field-by-field fill (no padding write).sys/kern/kern_sig.c:397โcopyout(sizeof(oact)).sys/sys/signal.h:221โstruct sigaction.sys/sys/_sigset.h:35โsigset_t=unsigned int[4](16 bytes).- CWE-908 Use of Uninitialized Resource; compare FreeBSD's padding-zeroing in
sigaction.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_sig.c. - pending Reported to DragonFlyBSD security contact.