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 保留给他们真的很有必要。
我有 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()
}
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 保留给他们真的很有必要。