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

pin_mapped/pin_opened protected by two independent locks: mutual-exclusion race

Field Value
ID DF-1847
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:N
CWE CWE-662 Improper Synchronization
File sys/dev/misc/gpio/gpio.c
Lines 196-227, 304-313, 409, 440
Area dev/misc (GPIO locking)
Confidence likely
Discovered 2026-07-20
Reported pending
Known CVE none
CVE match dfly_specific

Summary

The intended invariant is that a pin is either opened (via /dev) XOR mapped (via a consumer), never both. But pin_mapped is written under gpio_lock while pin_opened and the pin_opened || pin_mapped check are taken under a completely separate gpiodev_lock. Two root threads racing gpio_open and gpio_consumer_attach can both see the other flag as 0 and both succeed, breaking the invariant and allowing two writers to drive the same pin.

Root cause

gpio_map takes gpio_lock to set gp->pins[pin].pin_mapped = 1 (gpio.c:196-227). gpio_open (gpio.c:304-314), gpio_close (gpio.c:325-331), gpio_ioctl (gpio.c:409-440) and gpio_master_ioctl's GPIOPINSET (gpio.c:459-523) all take gpiodev_lock instead when checking/setting pin_opened and when reading pin_mapped. Neither lock is acquired while holding the other, so the check-then-set pairs in gpio_open (lines 308-313) and gpio_map (lines 214-225) are not atomic with respect to each other.

Race window:

T1 (gpio_open):               T2 (gpio_map):
lock(gpiodev_lock)
read pin->pin_mapped == 0
                              lock(gpio_lock)
                              read pin->pin_opened == 0
pin->pin_opened = 1
unlock(gpiodev_lock)
                              gp->pins[pin].pin_mapped = 1
                              unlock(gpio_lock)

Final state: both flags are 1.

Threat model & preconditions

  • Attacker position: root only (master and per-pin devs are 0600, gpio.c:574,580).
  • Privileges gained or impact: once both flags are set, both gpio_write (gpio.c:337-365) and the consumer's gpio_pin_write (gpio.c:280-284) can drive the same pin concurrently with no synchronization β€” a confused-deputy on the GPIO line. Root already controls both endpoints, so this is a correctness / hygiene issue, not a privilege escalation.
  • Required config or capabilities: device gpio; root access.
  • Reachability: two root threads racing open/close of a pin against attach/detach of a consumer on the same pin.

Proof of concept

# As root on a system with a GPIO controller and the gpio_led consumer:
# Thread A: hammer open/close of a specific pin
while true; do
    exec 3<>/dev/gpio/gpio0/<N>
    exec 3>&-
done &
# Thread B: hammer attach/detach of the led consumer on the same pin
while true; do
    gpioctl gpio0 attach led <N> 1 "x"
    gpioctl gpio0 detach led <N>
done &

After a short time, inspection of the kernel pin state (e.g. via the GPIOINFO leak DF-1844 or a kernel debugger) shows pin_opened == 1 && pin_mapped == 1 for the contended pin β€” the invariant is broken.

Impact

Info-level locking protocol defect. Root-vs-root correctness issue. No privilege escalation; documented to guide the fix.

Use a single lock to protect both pin_mapped and pin_opened. The minimal change is to have gpio_map and gpio_unmap acquire gpiodev_lock instead of gpio_lock for the pin_mapped check/set (and keep gpio_lock only for the consumer/driver lists), or to acquire both locks in a fixed order in every path that touches either flag. A common fix is to make gpio_open/gpio_ioctl/ gpio_master_ioctl also acquire gpio_lock (or to fold both locks into one).

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1847 Β· 1 files
FileTypeDescriptionSize
manifest.json file 389 B view raw

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

recommended fix identified; fix.diff not authored/validated in this batch

recommended fix identified; fix.diff not authored/validated in this batch
combined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Info severity)

Evidence (decisive lines)

Source-confirmed at sys/dev/misc/gpio/gpio.c:196: pin_mapped/pin_opened protected by two independent locks (mutual-exclusion race)

Verified recommended fix

Source-confirmed at sys/dev/misc/gpio/gpio.c:196: pin_mapped/pin_opened protected by two independent locks (mutual-exclusion race)

Verdict

Source-confirmed at sys/dev/misc/gpio/gpio.c:196: pin_mapped/pin_opened protected by two independent locks (mutual-exclusion race)