None of the workarounds (get users to upgrade to Monterey, build the dext on Xcode 12, etc.) seemed particularly attractive or sustainable, so I've finally done a deep dive on this and figured out the problem.
The change that introduces the crash is that starting with DriverKit 21, IOUserClient::ExternalMethod is annotated to run on a named dispatch queue rather than the default dispatch queue: QUEUENAME(IOUserClientQueueExternalMethod). According to the documentation, this queue is by default the dispatch queue, but I guess this association is only made in the DriverKit 21+ runtime, while macOS 11/DriverKit 20 doesn't have an entry for it in its dispatch queue name table by default.
You can work around the issue quite easily by adding the entry yourself, using something like:
IODispatchQueue* default_queue = nullptr;
kern_return_t res = uc->CopyDispatchQueue(kIOServiceDefaultQueueName, &default_queue);
if (res == KERN_SUCCESS && default_queue != nullptr)
{
res = uc->SetDispatchQueue(kIOUserClientQueueNameExternalMethod, default_queue);
}
OSSafeReleaseNULL(default_queue);
This explicitly sets the kIOUserClientQueueNameExternalMethod queue to the object's default queue. You'll want to do this during init or Start of any user client subclass, and it's of course not needed if you're otherwise explicitly setting the external method queue; make sure to get the order right if you're changing the user client's default queue and want external methods to run on the same queue.