Kext OSDynamicCast在OSObject :: free期间失败

时间:2018-03-27 07:50:27

标签: c++ macos kernel-extension xnu

我有从IOService基类派生的IOKit驱动程序,并且它的原始指针被传递给kauth框架中可能非常频繁调用的某个事件回调函数。

为了从指针中提取此实例,我使用安全方法OSDynamicCast,并确保在驱动程序拆解期间,我禁用kauth调用并清除所有现有调用,然后释放驱动程序。但是,有时我仍然会在OSDynamicCast

上遇到内核恐慌
frame #0: [inlined] OSMetaClass::checkMetaCast(check=0xffffff802b28d3f0)
frame #1: [inlined] OSMetaClassBase::metaCast(OSMetaClass const*) const
frame #2: kernel`OSMetaClassBase::safeMetaCast

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

也许有人知道在::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()
}

1 个答案:

答案 0 :(得分:1)

There is an inherent race condition in kauth_unlisten_scope。您的互斥解决方案几乎肯定无法完全解决问题,因为回调中的代码实际上可以在 kauth_unlisten_scope()返回后运行 - 换句话说,kauth回调还没有锁定互斥锁

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

如果你想要格外小心,你也可以添加一个通常为true的全局布尔标志,但在取消注册你的kauth监听器之前设置为false。您可以在锁定互斥锁等之前测试输入回调时的标志。如果该标志为false,则立即从回调中返回。这至少可以防止对动态分配的内存的访问;但是,原则上它仍然没有100%解决问题,因为当卸载kext时,全局变量当然会消失。

Apple在使用kauth API约7年后就已经知道了这个问题。他们还没有在那个时候修复它,因为他们计划在未来几年完全逐步取消关键时刻,我认为这不会改变。

旁注:

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

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

E.g。 BAD:

//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回调中非常重要。

确实如此,以至于我对在“热”路径上锁定互斥锁时要小心,你建议你这样做。 这意味着您实际上是在整个系统中的所有用户进程中对所有文件I / O进行单线程处理。不要将这种代码发送给客户。 考虑使用RW-Lock而只是在一般情况下锁定读取,并在真正需要时保留写锁。 / p>