gpio_unregister leaks the master device's clone unit and the shared struct gpio_driver (unlinked while sibling controllers still reference it)
Summary
Every controller attach/detach cycle leaks heap: the master cdev clone unit is never returned (no devfs_clone_bitmap_put for :573's get), and struct gpio_driver+name is LIST_REMOVE'd but never kfree'd - unconditionally, even while sibling controllers sharing the driver_name still reference it (no refcount), so the sibling's later unregister skips its unit put. Unbounded unit-space+heap creep per cycle; privileged trigger. Refs+put+conditional-free fix, compile-validated as KLD with -Werror.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4752 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| trigger.sh | β | 1.6 KB | view raw | |
| build.sh | β | 122 B | view raw | |
| run.sh | β | 152 B | view raw | |
| build.log | β | 5.9 KB | view raw | |
| fix_build.log | β | 5.9 KB | view raw | |
| run.log | β | 656 B | view raw | |
| env.txt | β | 702 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| fix_gpio.c.patched | β | 15.1 KB | β download | |
| manifest.json | β | 904 B | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-4752 β gpio(4) gpio_unregister leaks the master clone unit + struct gpio_driver
File: sys/dev/misc/gpio/gpio.c (pass 2)
What
gpio_unregister() (gpio.c:587-617) has two resource-accounting defects that
are distinct from the known pin-unit get-vs-put bug (DF-1845, gpio.c:596-600):
-
Master device clone unit never returned.
gpio_register()allocates a unit fromDEVFS_CLONE_BITMAP(gpio)for the master cdev (gpio.c:573-575,make_dev(..., "gpio/%s%d/master", ...)).gpio_unregister()destroys that dev (gpio.c:602) but never callsdevfs_clone_bitmap_put()for it β the unit stays allocated forever. (The pin loop right above at least attempts a bitmap call, albeit the wrong one.) -
struct gpio_driver+ name never freed, dropped from the list while siblings may still use it.gpdis allocated once per driver name (gpio.c:559-565, shared by all controllers with the samedriver_name;gpd->name = kstrdup(...)).gpio_unregister()doesdevfs_clone_bitmap_put(&gpd->unit_bitmap, ...); LIST_REMOVE(gpd, link);(gpio.c:609-611) with no kfree of gpd or gpd->name, and no refcount: - every unregister leakssizeof(struct gpio_driver)+ name (M_TEMP); - if two controllers share the name, the first unregister unlinks the sharedgpdwhile the sibling is still live; the sibling's own unregister then finds no list entry and silently skips its bitmap put.
Impact
Permanent kernel heap leak per controller attach/detach cycle plus monotonic
clone-unit consumption (the bitmap auto-extends β devfs_clone_bitmap_get()
is called with limit 0, which never fails β so this is unbounded creep, not a
hard exhaustion). Trigger is privileged (driver detach / kldunload), hence
Low severity, consistent with the DF-1845/DF-1846 family.
Reproduce (hardware/config-gated)
Run ./trigger.sh on a system with device gpio + a GPIO controller
(nsclpcsio/PC87366 etc.). On the audit guest the framework is not compiled in
(optional gpio, not in X86_64_GENERIC), so the trigger exits 3 (SKIP) β
defect verified by source trace; fix compile-validated (see VERDICT.md).
Fix
fix.diff (applies to sys/dev/misc/gpio/gpio.c): put the master unit back,
refcount gpd, free on last reference. Compile-validated as a kernel module
against the guest's 6.5-DEVELOPMENT source with -Werror (fix_build.log).
DF-4752 VERDICT
Status: INCONCLUSIVE (runtime) β defect certain by source trace; fix compile-validated. Runtime reproduction is hardware/kernel-config-gated on the audit guest.
Impact: Low β permanent kernel heap leak (struct gpio_driver + name +
one clone-bitmap word per cycle) on every GPIO controller detach/re-attach.
Privileged trigger only (driver detach / module unload). No memory-safety
consequence; same severity family as DF-1845/DF-1846.
Why it is real (source trace)
gpio.c:573unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(gpio), 0);β master unit allocated;gpio.c:574-575make_dev(..., unit, ... "gpio/%s%d/master", ...).gpio.c:596-602(unregister): the pin loop calls the bitmap (wrongly β DF-1845, known, NOT re-reported) anddestroy_dev(gp->master_dev);atgpio.c:602β nodevfs_clone_bitmap_putfor the master's unit anywhere in the function (verified by reading the whole function and byrg devfs_clone_bitmapover the file: register = 3 gets, unregister = 1 get(bug) + 1 put(driver_unit)).gpio.c:552-566(register): onegpdper driver name; second controller of the same name reuses it (master_unit = devfs_clone_bitmap_get(...)), no refcount kept.gpio.c:604-613(unregister):LIST_REMOVE(gpd, link); break;βgpdandgpd->nameneverkfreed (leak), and the unlink happens even when sibling controllers of the same name are still registered (they lose their put).devfs_clone_bitmap_get(bitmap, 0)(sys/vfs/devfs/devfs_helper.c): withlimit == 0theunit > limitguard is disabled β never returns -1; the bitmap auto-extends β consequence is unbounded unit-space/heap creep, not allocation failure.
Verification performed on the guest
- Feature-gating confirmed (run.log):
/dev/gpio*absent; no/boot/kernel/gpio.ko;sys/dev/misc/gpio/gpio.cisoptional gpio(sys/conf/files:2119) and X86_64_GENERIC has nodevice gpioβ the framework cannot be instantiated on this guest (needs a kernel withdevice gpioplus a controller such as nsclpcsio/PC87366 hardware). - Fix compile-validated: baseline
gpio.candfix.diff-patchedgpio.cboth built as KLD modules against the guest's pristine /usr/src (DragonFly 6.5-DEVELOPMENT) with-Werror: * build.log (baseline) β rc=0 * fix_build.log (patched) β rc=0 sys/tree untouched; guest /usr/src restored and hash-verified (c93a0e25β¦ for gpio.c, 3c863567β¦/22808bdeβ¦ for cxm.h/cxm.c) after the fix build.
Fix
fix.diff: add refs to struct gpio_driver, increment on reuse,
devfs_clone_bitmap_put() the master unit after destroy_dev(), and
kfree() gpd+name when the last reference drops.
Baseline vs patched
Not runtime-testable on this guest (feature absent). The patch is a pure
resource-accounting change; both variants compile clean with -Werror.
fix_status=inconclusive (build verified, behavior hardware-gated).
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
inconclusivefix.diff compiles clean as a KLD module (-Werror, rc=0) against guest 6.5-DEVELOPMENT source. Behavioral validation (unit returned, gpd freed, siblings safe) requires gpio(4) hardware absent from this guest.
fix_build.log (patched module link line + rc=0); build.log (baseline rc=0); fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (source trace with path:line); run.log (guest feature-absence markers); build.log vs fix_build.log (baseline and patched both rc=0, -Werror); fix.diff (refs + master-unit put + kfree on last ref)
PoC changes
No seed PoC existed; trigger.sh authored fresh and honestly exits SKIP=3 on guests without the gpio(4) framework.
Verified recommended fix
Put the master clone unit back after destroy_dev; refcount struct gpio_driver and kfree gpd+name when the last same-name controller unregisters.
Verdict
Source-certain defect: gpio_unregister (gpio.c:587-617) never returns the master device's DEVFS_CLONE_BITMAP(gpio) unit (allocated at gpio.c:573 for the 'gpio/
No comments yet.