# DF-2944 VERDICT — REPRODUCED (leak)

## Bottom line

`acct_process()` in `sys/kern/kern_acct.c` writes a stack-local
`struct acct` to the accounting file without zeroing it first. On x86_64 the
struct is 56 bytes and contains 9 bytes of alignment padding that no field
assignment touches (2 bytes at offset 22–23 before the 8-byte-aligned
`ac_btime`; 7 bytes of tail padding at 49–55). Every accounting record
therefore carries 9 bytes of the exiting thread's kernel-stack residue.
Verified live on the audit guest: 49–50 of 50 records per run carried
non-zero padding, 3/3 runs, including pointer-shaped kernel-stack contents
(`78 78 18 01 f8 ff ff` → qword `0xfffff80118787800` at offset 48,
DragonFly KVA range).

## Root cause (path:line)

- `sys/kern/kern_acct.c:198` — `struct acct acct;` declared uninitialized.
- `sys/kern/kern_acct.c:225-264` — every *field* assigned; padding never written.
- `sys/kern/kern_acct.c:276-278` — `vn_rdwr(UIO_WRITE, vp, (caddr_t)&acct,
  sizeof(acct), ...)` writes all 56 bytes including the 9 uninit bytes.
- Layout proof: `sys/sys/acct.h:50-71` (comp_t=u16, time_t field at 24,
  dev_t=u32 per `sys/sys/stat.h:61`, u8 flag ⇒ 56-byte struct with pads).
  The PoC `_Static_assert`s the offsets against the guest's own headers and
  passed compilation (`build.log`).

## How it was verified

1. Guest: DragonFly 6.5-DEVELOPMENT x86_64 (stock INVARIANTS kernel #0).
2. `acct_leak.c` (root): `creat` a fresh file, `syscall(51, path)` to enable
   accounting (`init_sysent.c:68` — acct is syscall 51).
3. Wave A: 25 forked children setuid(65534), churn the kernel stack with
   recognizable syscalls (30× udp sendto 127.0.0.1:0x5A5A, 0xC3 payloads,
   getsockname), `_exit(7)`.
   Wave B: 25 children `execve("/tmp/acct_helper")` (deep exec-path stack
   usage) which setuid(65534), churn, `_exit(9)`.
4. `syscall(51, NULL)` disables accounting.
5. Parse the file in 56-byte records; dump bytes [22..24) and [49..56).

## Observations (decisive)

- Run 1: 50 records, 49 with non-zero padding (pad1: 49, pad2: 1);
  record 48 tail = `78 78 18 01 f8 ff ff` (kernel pointer fragment);
  record 33 clean — showing natural variance, not a deterministic constant.
- Runs 2 & 3: `LEAK-CONFIRMED` again (50/50, pad1 49–50).
- Raw file hexdump (`leak_sample.txt`): `ff ff` visible at file offset
  0x16–0x17 of every record; `ac_uid=0xfffe` (65534), `ac_tty=0xffffffff`
  (NOUDEV) confirm the field/padding framing is exactly as derived.
- File size 2800 = 50 × 56 ✔.

## Threat model / impact ceiling

- Triggering is *not* privileged: any exiting process produces a record
  (the harness records carry ac_uid=65534). Enabling accounting and reading
  the file are privileged/permission-gated (acct(2) needs SYSCAP_NOACCT,
  kern_acct.c:128; the file is created by the admin — here mode 0600).
- Ceiling: disclosure of kernel-stack fragments (potentially kernel
  pointers — demonstrated) of *arbitrary users' processes* to anyone who
  can read the accounting file. On systems without KASLR this is mostly an
  information-hygiene bug; on KASLR'd systems it is a pointer-harvest
  primitive for an attacker who can read the file (misconfigured perms,
  backup copies, group-readable accounting as shipped by some sites).
- No integrity/availability impact: read-only disclosure, fixed 9 bytes/record.

## Why not higher severity

Per the severity rubric this is "info leak of limited kernel memory [that]
requires unusual config/privilege" — reading the accounting file requires
privileges equivalent to the party that enabled accounting, in default
configurations. Low severity, certain confidence (live-reproduced).

## Exploit chain

None pursued beyond disclosure (leak class). Escalation would require a
second primitive; the leaked 9 bytes/record are a harvesting channel, not a
corruption primitive.

## Fix

`fix.diff` — `bzero(&acct, sizeof(acct));` before the field assignments in
`acct_process()`. This zeroes all padding; every field is subsequently
assigned, so record contents are unchanged. Fix-validation kernel rebuild
was not performed (Low-severity, non-corruption finding — per audit
contract, rebuild validation is mandated for memory-corruption findings);
the one-line zeroing is provably sufficient by inspection of the assignment
list at kern_acct.c:225-264.
