โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0007

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.

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

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/kern_sig.c.
  • pending Reported to DragonFlyBSD security contact.