我有从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()
}
答案 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>