How to properly convert any size of application memory into the kernel space of Driverkit?

  • Hardware and software configuration

    MacBook Air M2 2022 16GB, MacOS Ventura 13.2.1

  • Full description

    This is a DriverKit that controls PCIE FPGA devices for low-latency data exchange. This driver has been implemented on Iokit, and now it needs to be launched on Driverkit to adapt to newer Macs.

    Driverkit lacks the IOMemoryDescriptor::withAddressRange(Iokit) function to convert the app's memory of any size to a Descriptor.

    Currently, we use args->structureOutputDescriptor->CreateMapping to map the Descriptor passed by the application to the kernel layer.

// App
size_t ***::xxRead(long long addr, size_t size, void * buff){
    std::lock_guard<std::mutex> guard(usrLock);
    kern_return_t   kr;
    uint64_t info[2] = {(uint64_t)addr, (uint64_t)size};
    kr = IOConnectCallMethod(
                             connect,
                             kUserReadIO,
                             info,
                             2,
                             NULL, NULL, NULL, NULL,
                             buff,
                             &size);

    return size;
}


// Driverkit
const IOUserClientMethodDispatch sMethods[kNumMethods] = {
    
    [kUserReadIO] =
    {
        (IOUserClientMethodFunction) &SmiPcieUc::sUserReadIo,
        .checkCompletionExists = false,
        .checkScalarInputCount = 2, // Read Addr, size
        .checkStructureInputSize = 0,
        .checkScalarOutputCount = 0,
        .checkStructureOutputSize = kIOUserClientVariableStructureSize} // Read Data
};
kern_return_t SmiPcieUc::sUserReadIo (OSObject * target, void* reference, IOUserClientMethodArguments* args){
    
    IOMemoryMap * memMap = nullptr;
    uint32_t * buffKptr = nullptr;
    kern_return_t rt = 0;
    
    if(target == nullptr){
        Log("***Err***: sUserReadIo Target is Null!");
        return kIOReturnError;
    }
    
    if(args->structureOutputDescriptor){
        rt = args->structureOutputDescriptor->CreateMapping(0,0,0,0,0, &memMap);
        if(rt == kIOReturnSuccess){
            buffKptr = reinterpret_cast<uint32_t *>(memMap->GetAddress());
        }
        else {
            Log("***Err***: sUserReadIo Mapping Failed!");
            return kIOReturnNoMemory;
        }
    } else {
        buffKptr = (uint32_t *) args->structureOutput;
    }
    
    rt = ((SmiPcieUc *)target)->UserReadIo((uint64_t *)&args->scalarInput[0], (size_t *)&args->scalarInput[1], buffKptr);
    OSSafeReleaseNULL(memMap);
    
    return rt;
}
  • phenomenon

    When StructureOutputSize is greater than 4096, args>structureOutputDescriptor exists, and when it is less than or equal to 4096, args->structureOutputDescriptor and args->structureOutput are both equal to nullptr, (in IOkit, args->structureOutput is not empty)。

How to properly convert any size of application memory into the kernel space of Driverkit?

You can use "CreateMemoryDescriptorFromClient" for this:

https://developer.apple.com/documentation/driverkit/iouserclient/3674819-creatememorydescriptorfromclient

More specifically, when you say this:

Driverkit lacks the IOMemoryDescriptor::withAddressRange(Iokit) function to convert the app's memory of any size to a Descriptor.

What "CreateMemoryDescriptorFromClient" actually does is call "IOMemoryDescriptor::withAddressRange", passing in the arguments you provide and using the task attached to the user client. It provides the same capability as "withAddressRange", except it also prevent DEXTs from interacting with processes they don't have a relationship with.

-Kevin Elliott
DTS Engineer, CoreOS/Hardware

How to properly convert any size of application memory into the kernel space of Driverkit?
 
 
Q