macosdriveriokitpcidriverkit

IOLock crashes mac OS when locking from IOUserService


We're using a data structure that requires locking and want to access it from an IOUserClient thread that runs when probing for a PCI device using PCIDriverKit.

  1. Is using IOLock not allowed in this kind of thread? When trying to use IOLock from an IOUserClient thread that is not using an IOPCIDevice provider, it worked without a crash.
  2. Is there any other ways to use locks from PCIDriverKit? Linking with pthreads.h seems pretty impossible (keep on getting errors as it is not part of the framework).

When calling IOLockLock we get a kernel crash stating:

panic(cpu 3 caller 0xffffff801a9c58f6): Kernel trap at 0xffffff801d6cbb33, type 14=page fault, registers:
CR0: 0x0000000080010033, CR2: 0x0000000000000028, CR3: 0x000000010cc6a074, CR4: 0x00000000000606e0
RAX: 0x0000000000000000, RBX: 0xffffff9359970250, RCX: 0xffffff86902e0c90, RDX: 0x0000000000000008
RSP: 0xffffffa05ff5bc60, RBP: 0xffffffa05ff5bc70, RSI: 0x0000000000000006, RDI: 0xffffff9359765000
R8:  0xffffff801b879b80, R9:  0xffffffc69a7581e0, R10: 0xffffff9359703800, R11: 0x0000000f9358cf60
R12: 0x000000000000001e, R13: 0x0000000000000046, R14: 0xffffff93596ee600, R15: 0xffffff8696094068
RFL: 0x0000000000010286, RIP: 0xffffff801d6cbb33, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0x0000000000000028, Error code: 0x0000000000000000, Fault CPU: 0x3 VMM, PL: 0, VF: 0

Backtrace (CPU 3), Frame : Return Address
0xffffffa05ff5b680 

Return Address
0xffffffa05ff5b680 : 0xffffff801a88e0dd mach_kernel : _handle_debugger_trap + 0x3fd
0xffffffa05ff5b6d0 : 0xffffff801a9d4f33 mach_kernel : _kdp_i386_trap + 0x143
0xffffffa05ff5b710 : 0xffffff801a9c552a mach_kernel : _kernel_trap + 0x55a
0xffffffa05ff5b760 : 0xffffff801a832a2f mach_kernel : _return_from_trap + 0xff
0xffffffa05ff5b780 : 0xffffff801a88d8fd mach_kernel : _DebuggerTrapWithState + 0xad
0xffffffa05ff5b8a0 : 0xffffff801a88dbf3 mach_kernel : _panic_trap_to_debugger + 0x273
0xffffffa05ff5b910 : 0xffffff801b09d81a mach_kernel : _panic + 0x54
0xffffffa05ff5b980 : 0xffffff801a9c58f6 mach_kernel : _sync_iss_to_iks + 0x2c6
0xffffffa05ff5bb00 : 0xffffff801a9c55dd mach_kernel : _kernel_trap + 0x60d
0xffffffa05ff5bb50 : 0xffffff801a832a2f mach_kernel : _return_from_trap + 0xff
0xffffffa05ff5bb70 : 0xffffff801d6cbb33 com.apple.iokit.IOUSBHostFamily : __ZN15IOUSBHostDevice4freeEv + 0x133
0xffffffa05ff5bc70 : 0xffffff801d69824d com.apple.iokit.IOUSBHostFamily : __ZN24AppleUSBHostLegacyClient4freeEv + 0x1b
0xffffffa05ff5bc90 : 0xffffff801d603c05 com.apple.iokit.IOUSBFamily : __ZN14AppleUSBDevice4freeEv + 0xf9
0xffffffa05ff5bce0 : 0xffffff801a97b7a1 mach_kernel : _iokit_notify + 0x141
0xffffffa05ff5bd20 : 0xffffff801a8935c2 mach_kernel : _ipc_kobject_server + 0x162
0xffffffa05ff5bd90 : 0xffffff801a869cf5 mach_kernel : _ipc_kmsg_send + 0x115
0xffffffa05ff5bdf0 : 0xffffff801a89489c mach_kernel : _mach_msg_send_from_kernel_proper + 0xbc
0xffffffa05ff5be20 : 0xffffff801a877f4a mach_kernel : _ipc_right_dealloc + 0x56a
0xffffffa05ff5bea0 : 0xffffff801a8821b8 mach_kernel : _mach_port_deallocate + 0x88
0xffffffa05ff5bed0 : 0xffffff801a87f1e3 mach_kernel : __kernelrpc_mach_port_deallocate_trap + 0x43
0xffffffa05ff5bef0 : 0xffffff801a9a982d mach_kernel : _mach_call_munger64 + 0x29d
0xffffffa05ff5bfa0 : 0xffffff801a833216 mach_kernel : _hndl_mach_scall64 + 0x16
      Kernel Extensions in backtrace:
         com.apple.iokit.IOUSBHostFamily(1.2)[BD7179E5-F2E2-3E86-83D5-F3EA5EE1EE3F]@0xffffff801d688000->0xffffff801d716fff
            dependency: com.apple.driver.AppleBusPowerController(1.0)[C9BC6F50-42B7-3CD3-8608-B8F8236D3243]@0xffffff801bc2e000->0xffffff801bc31fff
            dependency: com.apple.driver.AppleSMC(3.1.9)[1CE81438-E589-30EA-B40B-615AC3BDBED4]@0xffffff801bf56000->0xffffff801bf6efff
            dependency: com.apple.driver.usb.AppleUSBCommon(1.0)[A8CD0277-B482-3874-8AA0-C7177FE83D1E]@0xffffff801c0a6000->0xffffff801c0a9fff
            dependency: com.apple.driver.AppleUSBHostMergeProperties(1.2)[6086D16D-B5BA-3D7F-9EAD-F6E9E56459E4]@0xffffff801d783000->0xffffff801d783fff
            dependency: com.apple.iokit.IOACPIFamily(1.4)[1B2FE91E-3EC7-3ED3-AF6B-E9C4BE29D5E3]@0xffffff801ce42000->0xffffff801ce43fff
         com.apple.iokit.IOUSBFamily(900.4.2)[7215E3ED-95C3-3298-BB93-2458C14F1DAF]@0xffffff801d5e1000->0xffffff801d646fff
            dependency: com.apple.driver.usb.AppleUSBCommon(1.0)[A8CD0277-B482-3874-8AA0-C7177FE83D1E]@0xffffff801c0a6000->0xffffff801c0a9fff
            dependency: com.apple.iokit.IOPCIFamily(2.9)[99A70889-A31C-3B25-8E88-ADD3F317E4E4]@0xffffff801d36a000->0xffffff801d392fff
            dependency: com.apple.iokit.IOUSBHostFamily(1.2)[BD7179E5-F2E2-3E86-83D5-F3EA5EE1EE3F]@0xffffff801d688000->0xffffff801d716fff

Process name corresponding to current thread: airportd
Boot args: -v keepsyms=1 tlbto_us=0 vti=9 

Thanks


Update: Eventually the crash was from a null pointer exception in my DEXT. My lesson is that such an exception in a DEXTs can crash your Mac completely, and the logs won't be informative at all (as you see). Best thing to do is debug with LLDB.


Solution

    1. If you're hitting a kernel panic without loading any 3rd party kexts, that's a bug in macOS, and you should report it to Apple. This is irrespective of anything I say below. Trigger a panic without kernel code? Not your fault, this shouldn't be possible, report it or it won't be fixed. Especially if you have a way to trigger it fairly reliably. File a DTS TSI if this issue is costing you money.
    2. That crash stack doesn't look like it's directly triggered by a call from a dext. So if your lock is indeed causing the crash, it's somehow incidental - probably a memory corruption issue - so it's hard to say what specifically about your code is triggering the problem. Just because it turns up when you lock in a certain place, that doesn't mean it's caused by that. It could also be caused by the resulting timing changes.
    3. In dexts, consider using OSAction for synchronisation, and note that you can schedule to handle methods on a specific dispatch queue by marking the method with the QUEUENAME macro in the .iig file. Unfortunately, all external methods are dispatched through the single ExternalMethod method, so if you need a single user client to execute external methods on different threads, you can't do that as far as I'm aware.
    4. Make sure you haven't hit a race condition inside your dext (again, it shouldn't KP if you have, but bear with me) - by default, each object has its own unique default queue - this means that methods in the main driver object and in the user client object will run on different threads so if the two objects access each other, that must be done in a threadsafe way, or you need to explicitly use the same queue as the driver service for all user client instances.