Kext OSDynamicCast 在 OSObject::free 期间失败

Kext OSDynamicCast failed during OSObject::free

我有 IOKit driver 派生自 IOService 基础 class,它的原始指针从 kauth 框架传递到某个事件回调函数,可能非常频繁地被调用。

为了从指针中提取这个实​​例,我使用安全方法 OSDynamicCast,并确保在 driver 拆解期间,我禁用 kauth 调用并刷新释放 driver 之前的所有现有调用。但是,有时我仍然会在 OSDynamicCast 上遇到内核恐慌:

frame #0: [inlined] OSMetaClass::checkMetaCast(check=0xffffff802b28d3f0)
frame #1: [inlined] OSMetaClassBase::metaCast(OSMetaClass const*) const
frame #2: kernel`OSMetaClassBase::safeMetaCast

当我在 IOService::stop 回调的 OSObject::free 之前禁用并刷新 kauth 调用时,问题没有重演(至少在尝试了数十次之后)。

也许有人知道在 ::stop::free 之间的时间段内是否有任何内存被释放会触发此恐慌?

这是一个小代码,当它触发恐慌时强调我的设计。

kauth_callback(kauth_cred_t credential, 
               void *idata, /* This is the RAW pointer for my IOService based instance */
               kauth_action_t action,
               uintptr_t arg0, 
               uintptr_t arg1, 
               uintptr_t arg2, 
               uintptr_t arg3)
{
   ...
   // taking shared_lock with mutex 
   auto my_inst = OSDynamicCast(com_my_driver, reinterpret_cast<OSObject *>(idata));
   ...
}


void com_my_driver::free(IOService *provider) 
{
     kauth_unlisten_scope(my_listener_);
     // taking unique lock with mutex to make sure no outstanding kauth calls.
     super::free(provider); //calling OSObject free
}

如果我将逻辑从 ::free 移动到 ::stop 它会起作用:

void com_my_driver::stop(IOService *provider) 
{
     kauth_unlisten_scope(my_listener_);
     // taking unique lock with mutex to make sure no outstanding kauth calls.
     super::stop(provider); // Calling IOService::stop()
}

。您的互斥解决方案几乎肯定不能完全解决问题,因为您在回调中的代码实际上可以 运行 after kauth_unlisten_scope() returns - 在其他换句话说,kauth 回调还没有锁定互斥量。

你所能做的就是在 kauth_unlisten_scope() return 秒后睡一会儿。希望在一秒钟左右后,所有 kauth 回调都已成功完成。

如果您想格外小心,您还可以添加一个全局布尔标志,该标志通常为 true,但在您注销 kauth 侦听器之前设置为 false。您可以在锁定互斥锁等之前进入回调时测试标志。如果该标志为假,则立即从回调中 return 。这至少可以防止对动态分配的内存进行任何访问;但是原则上还是不能100%解决问题,因为卸载kext时全局变量当然会消失

Apple 在我使用 kauth API 的大约 7 年里就知道了这个问题;他们当时还没有修复它,并且由于他们计划在未来几年内完全淘汰 kexts,我认为这不会改变。

旁注:

不要使用 reinterpret_cast<> 从不透明的 void* 转换为具体的指针类型。 static_cast<> 就是为此目的而设计的。

此外,假设您始终将类型 com_my_driver 的对象传递给 kauth_listen_scope(),您可以使用静态转换而不是动态转换。事实上,你应该对最初降级为 void* 的静态类型执行 static_cast<>,我怀疑你的 不是 OSObject案件。如果这与您期望的动态类型不同,请将其结果转换为派生类型。

例如坏:

//startup
com_my_driver* myobj = …;
// com_my_driver* implicitly degrading to void*
kauth_listen_scope("com_my_driver", kauth_callback, myobj);
// …
int kauth_callback(kauth_cred_t credential, 
    void *idata,
    kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
    // the void* came from a com_my_driver*, not an OSObject*!
    auto my_inst = static_cast<com_my_driver*>(static_cast<OSObject *>(idata));
}

更好:

int kauth_callback(kauth_cred_t credential, 
    void *idata,
    kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
    auto my_inst = static_cast<com_my_driver*>(idata);
}

这是一个相当小的问题,假设你没有对多重继承做任何事情,你不应该在 IOKit 中做任何事情,它会编译成在实践中使用相同的代码,但它避开了未定义的行为。此外,不正确的代码更难以阅读,如果您使用 OSDynamicCast,效率会降低 - 而效率在 kauth 回调中极为重要。

确实如此之多,以至于我什至会担心在 "hot" 路径上锁定互斥锁,您建议您这样做。 这意味着您实际上是 single-threading 所有文件 I/O 跨整个系统中的所有用户进程。不要将那种代码发送给客户。 考虑使用 RW-Lock 代替,并且在一般情况下只锁定读取,而 write-locks 保留给他们真的很有必要。