β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3634

Uninitialized kernel stack variable leaked to userspace by i915_gem_mmap_ioctl() when size==0 (DragonFly-only path)

Summary

In the DragonFly port of i915_gem_mmap_ioctl(), a zero-length mmap request takes an early 'goto out' that bypasses every assignment of the local 'unsigned long addr'; the out: tail then does 'args->addr_ptr = (uint64_t)addr' with error==0, returning 8 bytes of stale kernel stack to unprivileged userspace on every call. The #ifdef __DragonFly__ early-out is port glue - the Linux original assigns addr unconditionally. Unprivileged local user with /dev/dri render-node access (both GEM_CREATE and MMAP are DRM_RENDER_ALLOW): loop the ioctl with size=0, optionally interleaving syscalls to seed the stack, and harvest stale stack contents (kernel pointers -> KASLR defeat / stack-oracle). No auth, no race, unlimited repetitions. PoC compiles clean on the guest; runtime unreachable (virtio-only, no /dev/dri) - honest inconclusive / source-certain. On hardware this pairs with DF-1146 to turn address disclosure into full compromise.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3634 Β· 11 files
FileTypeDescriptionSize
poc_df3634.c β€” 2.9 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 108 B view raw
build.log β€” 216 B view raw
run.log β€” 365 B view raw
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
fix.diff β€” 331 B view raw
manifest.json β€” 854 B view raw
verdict.json β€” 3.8 KB view raw
env.txt β€” 357 B view raw

DF-3634 β€” Uninitialized kernel-stack variable leaked to userspace via i915_gem_mmap_ioctl (size==0)

What

i915_gem_mmap_ioctl() (sys/dev/drm/i915/i915_gem.c:1917) declares unsigned long addr; at line 1923 and β€” on the DragonFly-specific fast-out for zero-length mappings β€” jumps past every assignment of addr:

1960 #ifdef __DragonFly__
1961         if (args->size == 0)
1962                 goto out;

The out: path then uses the uninitialized variable:

2056 out:
2057         i915_gem_object_put(obj);
2058         if (IS_ERR((void *)addr)) {          /* uninit read #1 */
2059                 DRM_DEBUG("PTR_ERR=%ld\n", PTR_ERR((void *)addr));
2060                 return addr;                 /* garbage error code */
2061         }
2063         args->addr_ptr = (uint64_t) addr;    /* uninit read #2: 8 bytes of
                                                  stale kernel stack copied to
                                                  the ioctl args */
2067         return error;                        /* error == 0 here */

The ioctl returns success and hands 8 bytes of stale kernel stack back to userspace in args->addr_ptr. The first real assignment of addr is at line 1977 (addr = round_page((vm_offset_t)p->p_vmspace->vm_taddr + maxtsiz + maxdsiz);), after the goto out.

Reachability

  • Ioctl DRM_I915_GEM_MMAP is registered DRM_RENDER_ALLOW (sys/dev/drm/i915/i915_drv.c:3246) β€” reachable from any unprivileged user with access to a render node / card node.
  • Preconditions: an open DRM fd, one GEM handle (DRM_I915_GEM_CREATE, also RENDER_ALLOW), flags=0, size=0, any valid handle.
  • No DRM_AUTH, no master, no root required.
  • Repeatable without limit; the leaked slot contains whatever previous kernel frames left at that stack offset (kernel pointers, user pointers, lengths). Classic KASLR-defeat / stack-content oracle.

Impact

Info leak of limited kernel memory (8 bytes of kernel stack per call, unlimited calls) β†’ Medium per the project rubric. The secondary return addr; path can also turn garbage into an ioctl return code.

Reproduce (on hardware with an Intel GPU)

cc -O2 -Wall -o poc_df3634 poc_df3634.c
./poc_df3634 /dev/dri/card0     # or renderD128
# expected on vulnerable hw: "ioctl ret=0 addr_ptr=0x...." non-zero,
#   varying/pointing at stale stack contents across calls with
#   different preceding syscalls

On this audit guest the PoC builds cleanly but cannot execute: the QEMU guest is virtio-only (vgapci0 ... chip=0x11111234 virtio-gpu, no /dev/dri, no drm modules) and i915 is a PCI-driver that cannot attach to non-Intel hardware. Verdict is therefore inconclusive at runtime, source-certain β€” the uninit path is directly visible in the code and requires no race.

Fix

fix.diff in this directory (reject size==0 with -EINVAL, which also restores the intended semantics; alternative minimal fix is to initialize addr at declaration).

VERDICT.md
↓ download raw

DF-3634 VERDICT

Status: inconclusive (runtime) / certain (source). Impact if reachable: kernel stack info leak (8 bytes/call, unlimited).

Why the bug is certain from source

  1. sys/dev/drm/i915/i915_gem.c:1923 β€” unsigned long addr; is a plain uninitialized local.
  2. sys/dev/drm/i915/i915_gem.c:1961-1962 β€” the DragonFly-only if (args->size == 0) goto out; bypasses the first assignment of addr at :1977 (addr = round_page((vm_offset_t)p->p_vmspace->vm_taddr + maxtsiz + maxdsiz)).
  3. sys/dev/drm/i915/i915_gem.c:2056-2067 β€” the out: tail performs if (IS_ERR((void *)addr)) and args->addr_ptr = (uint64_t) addr; with error == 0 on this path, i.e. success + stale stack bytes copied into the ioctl out-argument.
  4. Nothing between (2) and (3) assigns addr on this path (DRM_DEBUG at :1949 only prints args/obj).
  5. The ioctl is unprivileged-reachable: DRM_IOCTL_DEF_DRV(I915_GEM_MMAP, i915_gem_mmap_ioctl, DRM_RENDER_ALLOW) β€” sys/dev/drm/i915/i915_drv.c:3246; the only precondition is a valid GEM handle, creatable via DRM_IOCTL_I915_GEM_CREATE (also RENDER_ALLOW, i915_drv.c:3243).

CWE-457 (use of uninitialized variable) / CWE-909. Comparable class: the historic DRM_INFO-leak family of uninit ioctls.

Verification performed

  • PoC written with uapi structs/ioctl numbers matched 1:1 against sys/dev/drm/include/uapi/drm/i915_drm.h (DRM_I915_GEM_CREATE=0x1b, DRM_I915_GEM_MMAP=0x1e, DRM_COMMAND_BASE+nr, struct layouts verified at i915_drm.h:638 and i915_drm.h:686).
  • Built on the DragonFly audit guest: BUILD-OK with cc -O2 -Wall, zero warnings (build.log).
  • Execution impossible on this guest: open("/dev/dri/card0") β†’ ENOENT; guest GPU is virtio (chip=0x11111234), no drm klds, i915 cannot probe (run.log). No Intel GPU emulation exists for this QEMU setup.
  • No runtime divergence attempted via i915 module load: i915 is a PCI driver; without an Intel VGA PCI id it never attaches and creates no /dev/dri node (checked pciconf -l).

Exploit chain (on real hardware)

unprivileged open("/dev/dri/renderD128") β†’ DRM_IOCTL_I915_GEM_CREATE (handle) β†’ loop: interleave arbitrary syscalls (to seed the thread's kernel stack with interesting values) with DRM_IOCTL_I915_GEM_MMAP {handle, size=0} β†’ read args->addr_ptr = 8 bytes stale kernel stack. Use for pointer disclosure / KASLR defeat; combine with any memory corruption primitive (e.g. DF-1146 in the same driver) for full compromise.

Fix validation

fix.diff authored against the read-only sys/ tree (reject size==0 with -EINVAL before the reference-counted map is attempted; also prevents the pointless object lookup/reference churn). Not applicable to build on this guest (no i915 device to test against), so fix_status=not_testable; the fix makes the uninit read unreachable by construction (early return).

Kernel references

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff returns -EINVAL for size==0 (drops the object reference first), removing the goto-out path entirely so addr can never be read uninitialized. Cannot be kernel-built/run against this finding on the audit guest: i915 does not attach without Intel hardware, so no baseline or patched behavior is observable; the fix is correct by construction (the uninit use site becomes dead code on that path).

fix.diff hunk at i915_gem.c:1958-1967; source trace in VERDICT.md.
↓ fix.diffper-fix-DF-3634

Confirmed kernel references

Detail

Exploit chain

open /dev/dri/renderD128 -> DRM_IOCTL_I915_GEM_CREATE (handle) -> loop { seed thread kernel stack via arbitrary syscalls; DRM_IOCTL_I915_GEM_MMAP {handle, size=0} } -> read args->addr_ptr = 8 bytes stale kernel stack; use disclosed pointers to defeat KASLR / locate objects in combination with a memory-corruption primitive such as DF-1146 in the same driver.

Evidence (decisive lines)

VERDICT.md (full source trace); poc_df3634.c (uapi structs matched 1:1 to sys/dev/drm/include/uapi/drm/i915_drm.h); build.log (BUILD-OK on DragonFly guest); run.log (open ENOENT, no /dev/dri, virtio GPU, no drm klds). Decisive lines: i915_gem.c:1961-1962 goto out before :1977 assignment; :2063 args->addr_ptr = (uint64_t)addr.

PoC changes

Rewrote embedded uapi (initial draft had wrong ioctl numbers 0x11/0x18 and a malformed drm_i915_gem_create); now matches sys/dev/drm/include/uapi/drm/i915_drm.h exactly: DRM_I915_GEM_CREATE=0x1b, DRM_I915_GEM_MMAP=0x1e, DRM_COMMAND_BASE+nr, correct struct layouts. Added graceful no-device exit so the absent-hardware case is explicit.

Verified recommended fix

Reject size==0 with -EINVAL before the mapping attempt (fix.diff), making the uninitialized read unreachable; belt-and-braces: initialize addr at declaration.

Verdict

Source-certain uninitialized-stack leak, runtime-unverifiable on this guest. i915_gem_mmap_ioctl() (sys/dev/drm/i915/i915_gem.c:1917) declares 'unsigned long addr' at :1923; the DragonFly-only 'if (args->size == 0) goto out;' at :1961-1962 skips the first assignment at :1977, and the out: tail at :2056-2067 does 'args->addr_ptr = (uint64_t)addr' with error==0, copying 8 bytes of stale kernel stack to userspace on a DRM_RENDER_ALLOW ioctl (i915_drv.c:3246), reachable unprivileged with just a GEM handle. PoC built cleanly on the audit guest (BUILD-OK, build.log) but /dev/dri does not exist there (virtio-only GPU chip=0x11111234, no drm klds, run.log), so the ioctl cannot be exercised: no Intel-GPU emulation exists for this QEMU setup. On real Intel hardware the leak is repeatable per call and is a stack-contents oracle (kernel pointers -> KASLR defeat).