Skip to content

Use-after-free in setKeyboard: CFRelease on the value from IOHIDDeviceGetValue #23

Description

@jamiusaliu

setKeyboard releases the value it gets from IOHIDDeviceGetValue:

https://github.com/damieng/setledsmac/blob/f117466/Source/SetLEDs/main.c#L149

IOHIDDeviceGetValue is a Get function, so by the CoreFoundation ownership rule
the caller does not own what it returns. The element hands back its own cached
IOHIDValueRef with a retain count of 1, so the release frees an object the
element still points at. The IOHIDDeviceSetValue a few lines later then makes
IOKit release that freed value internally.

I hit this by accident while using setleds on a loop, and found crashes already
sitting in ~/Library/Logs/DiagnosticReports.

Crash

0  libobjc.A.dylib   objc_release        EXC_BAD_ACCESS / KERN_INVALID_ADDRESS
1  IOKit             _IOHIDElementSetValue
2  IOHIDLib
3  IOHIDLib
4  IOKit             IOHIDDeviceSetValue
5  setleds           setKeyboard
6  setleds           setAllKeyboards
7  setleds           parseOptions
8  setleds           main

Four reports, all identical.

Ownership

Reading the same element three times without releasing:

read 1: ptr=0x7fd1f3028ab0  retainCount=1
read 2: ptr=0x7fd1f3028ab0  retainCount=1
read 3: ptr=0x7fd1f3028ab0  retainCount=1

after CFRelease: retainCount=1152921504606846975   (0x0FFF..., freed)
next iteration:  SIGSEGV

Same pointer each time, retain count 1. That single reference belongs to the
element.

Reproduction

Extracted lines 141 to 166 into a standalone file and built it twice, the only
difference being whether that one CFRelease is compiled in:

with CFRelease:      10/10 runs crashed,  0/10 completed 20 cycles
without CFRelease:    0/10 runs crashed, 10/10 completed 20 cycles

With the line removed, the real binary survives 100 alternating sets under
MallocScribble.

Why it looks intermittent

Each run over-releases once per LED element and exits immediately, so whether it
crashes depends on the freed block being reused in time. It also needs an actual
set: setleds -v alone never reaches IOHIDDeviceSetValue and never crashes,
which is why it is easy to miss.

Fix

Deleting line 149 is sufficient. Nothing else needs to change, and there is no
leak, because the value was never owned.

Environment

macOS 26.6.2 (25G83), Intel, setleds 0.4 built from f117466 with the supplied
Makefile.

Not verified

Happy to send a PR with just this one line if it would be useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions