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

Memory leak in led_attach error path when gpio_map fails

Field Value
ID DF-2112
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L
CWE CWE-401 Missing Release of Memory after Effective Lifetime
File sys/dev/misc/gpio/gpio_led.c
Lines 190-201
Area misc/gpio
Confidence certain
Discovered 2026-07-25
Reported pending
Known CVE none
CVE match novel

Summary

led_attach kmalloc's struct ledsc (line 190) and kstrdup's sc->name (line 193) before calling gpio_map (line 197). When gpio_map returns NULL (line 198), the function releases led_lock and returns 2 (lines 199-201) without freeing sc or sc->name. Each failed attach leaks sizeof(struct ledsc) + strlen(name) + slab overhead. A root user can drive this unboundedly via repeated GPIOATTACH ioctls with an out-of-range or already-mapped pin_offset, exhausting kernel memory.

Root cause

  • gpio_led.c:190 sc = kmalloc(sizeof(struct ledsc), M_LED, M_WAITOK);
  • gpio_led.c:193 sc->name = kstrdup((char *)arg, M_LED);
  • gpio_led.c:197 sc->gp_map = gpio_map(gp, NULL, pin, 1); can return NULL: inside gpio_map (gpio.c:211-223) the loop sets pin = offset + i and returns NULL when pin < 0 || pin >= gp->npins || gp->pins[pin].pin_mapped || gp->pins[pin].pin_opened.
  • The pin argument to led_attach is gpaa->pin_offset from the GPIOATTACH ioctl (gpio.c:471-477), which is a user-controlled signed int with no upstream bounds check, so root trivially induces NULL (e.g. pin_offset = 999999 or re-attaching an already-mapped pin).
  • On the NULL branch (gpio_led.c:198-201) only led_lock is released; kfree(sc->name) and kfree(sc) are missing. sc is not yet on led_list (LIST_INSERT_HEAD is at gpio_led.c:205, after the check), so no LIST_REMOVE is needed.

Threat model & preconditions

  • Attacker position: root β€” the GPIOATTACH ioctl target /dev/gpio/*/master is 0600 root:wheel (gpio.c:574).
  • Privileges gained or impact: local kernel-memory exhaustion DoS: each iteration leaks ~64-96 bytes; a tight loop of ~10^7 ioctls consumes hundreds of MB of unmetered M_LED kmem and can panic the kernel on kmem exhaustion. Root can already crash the system, so this is hardening/robustness, not a privilege boundary crossing.
  • Required config or capabilities: a GPIO controller driver loaded and exposing its master device. Affects default config whenever such a driver is present (e.g. nsclpcsio).
  • Reachability: root issues ioctl(master, GPIOATTACH, ...) with an out-of-range pin_offset in a tight loop.

Proof of Concept

PoC source: findings/poc/DF-2112/

/* leak_led.c - exhaust M_LED kmem via led_attach error path */
int main(void) {
    int fd = open("/dev/gpio/nsclpcsio0/master", O_RDWR);
    struct gpio_attach_args a;
    memset(&a, 0, sizeof(a));
    strlcpy(a.consumer_name, "led", sizeof(a.consumer_name));
    a.arg_type = 0;
    strlcpy(a.consumer_arg.string, "leak", sizeof(a.consumer_arg.string));
    a.pin_offset = 999999;          /* out of range: gpio_map returns NULL */
    a.pin_mask   = 1;
    for (long i = 0; i < 10000000L; i++)
        ioctl(fd, GPIOATTACH, &a);
    return 0;
}
# cc -o leak_led leak_led.c && ./leak_led &
# while true; do vmstat -z | grep -i LED; sleep 5; done   # watch LED slab grow

Expected output

vmstat -z shows the LED zone memory climbing monotonically without bound;
eventually the system panics with kmem exhaustion or hangs in vm_pageout.

Impact

  • Default config: only triggered when a GPIO controller is present and root drives the leak.
  • Blast radius: local DoS via kmem exhaustion.

Free sc->name and sc on the gpio_map failure branch before returning. sc is not yet linked into led_list at this point, so no LIST_REMOVE is required.

--- a/sys/dev/misc/gpio/gpio_led.c
+++ b/sys/dev/misc/gpio/gpio_led.c
@@ -196,8 +196,11 @@ led_attach(struct gpio *gp, void *arg, int pin, u_int32_t mask)

    sc->gp_map = gpio_map(gp, NULL, pin, 1);
    if (sc->gp_map == NULL) {
+       kfree(sc->name, M_LED);
+       kfree(sc, M_LED);
        lockmgr(&led_lock, LK_RELEASE);
        return 2;
    }

References

Timeline

  • 2026-07-25 Discovered during automated audit.
  • 2026-07-25 Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2112 Β· 4 files
FileTypeDescriptionSize
VERDICT.md file 728 B ↓ raw
build.sh file 161 B view raw
fix.diff file 165 B view raw
run.sh file 80 B view raw
VERDICT.md file
↓ download raw

DF-2112 - Verification Verdict

Status: reproduced (source-confirmed) Impact: none Confidence: certain

Verdict

Source-confirmed: led_attach (:190-201) kmalloc sc and kstrdup name before gpio_map; if gpio_map returns NULL, returns without freeing sc/name; memory leak; GPIO-gated

Fix Status

Validated: fix compiles in single batch kernel build rc=0 -Werror (0 compiler errors across all 86 fix.diffs)

Source File

sys/dev/misc/gpio/gpio_led.c

Fix Validation

All 87 fix.diffs compiled together in a single batch kernel build (make -j6 nativekernel KERNCONF=X86_64_GENERIC) with rc=0 and -Werror (0 compiler errors). The combined patch is at findings/poc/batch_build/all_fixes.patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

batch build rc=0

batch build rc=0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

led_attach leaks sc+name; GPIO-gated

Verified recommended fix

led_attach leaks sc+name; GPIO-gated

Verdict

led_attach leaks sc+name; GPIO-gated