How to map memory in DriverKit using IOMemoryDescriptor::CreateMapping?

1.4k views Asked by At

I am trying to learn more about DriverKit and memory management, and I read this question:

How to allocate memory in a DriverKit system extension and map it to another process?

And I would like to understand how to use IOMemoryDescriptor::CreateMapping.

I wrote a little app to test this where I do (very simplified code):

uint8_t * buffer = new uint8_t[256];
for (int i = 0 ; i < 256 ; i++)
   buffer[i] = 0xC6;

clientData in, out;
in.nbytes = 256;
in.pbuffer = buffer;
size_t sout = sizeof(out);

IOConnectCallStructMethod(connection, selector,&in,sizeof(in),&out,&sout);

// out.pbuffer now has new values in it

In my Kext user client class, I was doing (I am simplifying):

IOReturn UserClient::MyExtFunction(clientData * in, clientData * out, IOByteCount inSize, IOByteCount * outSize)
{
   MyFunction(in->nBytes, in->pbuffer);//this will change the content of pbuffer

   *out = *in; 
}


IOReturn UserClient::MyFunction(SInt32 nBytesToRead,void* pUserBuffer,SInt32* nBytesRead)
{
    PrepareBuffer(nBytesToRead,&pBuffer);
    ...
    (call function that will fill pBuffer)
}


IOReturn UserClient::PrepareBuffer(UInt32 nBytes,void** pBuffer);
{
   IOMemoryDescriptor * desc = IOMemoryDescriptor::withAddressRange((mach_vm_address_t)*pBuffer,nBytes,direction, owner task);

   desc->prepare();
   IOMemoryMap * map = desc->map();
   *pBuffer = (void*)map->getVirtualAddress();

   return kIOReturnSuccess;
}

This is what I don't know how to reproduce in a DExt and where I think I really don't understand the basic of CreateMapping.

Or is what I used to do not possible?

In my driver, this is where I don't know how to use CreateMapping and IOMemoryMap so this buffer can be mapped to a memory location and updated with different values.

I can create an IOBufferMemoryDescriptor but how do I tie it to the buffer from my application? I also don't understand the various options for CreateMapping.

Please note that in another test app I have successfully used IOConnectMapMemory64()/CopyClientMemoryForType() but I would like to learn specifically about CreateMapping().

(I hope it is alright I edited this question a lot... still new to StackOverflow)

2

There are 2 answers

2
pmdj On

Or is what I used to do not possible?

In short, no.

You're attempting to map arbitrary user process memory, which the client application did not explicitly mark as available to the driver using IOKit. This doesn't fit with Apple's ideas about safety, security, and sandboxing, so this sort of thing isn't available in DriverKit.

Obviously, kexts are all-powerful, so this was possible before, and indeed, I've used the technique myself in shipping drivers and then ran into trouble when porting said kexts to DriverKit.

The only ways to gain direct access to the client process's memory, as far as I'm aware, are:

  • By passing buffers >= 4097 bytes as struct input or output arguments to IOConnectCall…Method()s so they arrive as IOMemoryDescriptors in the driver. Note that these can be retained in the driver longer term, but at least for input structs, updates on the user space side won't be reflected on the driver side as a copy-on-write mapping is used. So they should be used purely for sending data in the intended direction.
  • By the user process mapping an existing IOMemoryDescriptor into its space using IOConnectMapMemory64()/CopyClientMemoryForType().

This does mean you can't use indirect data structures like the one you are using. You'll have to use "packed" structures, or indices into long-lasting shared buffers.

By "packed" structures I mean buffers containing a header struct such as your clientData which is followed in contiguous memory by further data, such as your buffer, referencing it by offset into this contiguous memory. The whole contiguous memory block can be passed as an input struct.

I have filed feedback with Apple requesting a more powerful mechanism for exchanging data between user clients and dexts; I have no idea if it will be implemented, but if such a facility would be useful, I recommend you do the same. (explaining what you'd like to use it for with examples) The more of us report it, the more likely it'll happen. (IOMemoryDescriptor::CreateSubMemoryDescriptor() was added after I filed a request for it; I won't claim I was the first to do so, or that Apple wasn't planning to add it prior to my suggestion, but they are actively improving the DriverKit APIs.)

Original answer before question was edited to be much more specific:

(Retained because it explains in general terms how buffer arguments to external methods are handled, which is likely helpful for future readers.)

Your question is a little vague, but let me see if I can work out what you did in your kext, vs what you're doing in your dext:

  • You're calling IOConnectCallStructMethod(connection, selector, buffer, 256, NULL, NULL); in your app. This means buffer is passed as a "struct input" argument to your external method.
  • Because your buffer is 256 bytes long, which is less than or equal to sizeof(io_struct_inband_t), the contents of the buffer is sent to the kernel in-band - in other words, it's copied at the time of the IOConnectCallStructMethod() call.
  • This means that in your kext's external method dispatch function, the struct input is passed via the structureInput/structureInputSize fields in the incoming IOExternalMethodArguments struct. structureInput is a pointer in the kernel context and can be dereferenced directly. The pointer is only valid during execution of your method dispatch, and can't be used once the method has returned synchronously.
  • If you need to use the buffer for device I/O, you may need to wrap it in an IOMemoryDescriptor. One way to do this is indeed via IOMemoryDescriptor::CreateMapping().
  • If the buffer was 4097 bytes or larger, it would be passed via the structureInputDescriptor IOMemoryDescriptor, which can either be passed along to device I/O directly, or memory-mapped for dereferencing in the kernel. This memory descriptor directly references the user process's memory.

DriverKit extensions are considerably more limited in what they can do, but external method arguments arrive in almost exactly the same way.

  • Small structs arrive via the IOUserClientMethodArguments' structureInput field, which points to an OSData object. You can access the content via the getBytesNoCopy()/getLength() methods.
  • If you need this data in an IOMemoryDescriptor for onward I/O, the only way I know of is to create an IOBufferMemoryDescriptor using either IOUSBHostDevice::CreateIOBuffer() or IOBufferMemoryDescriptor::Create and then copying the data from the OSData object into the buffer.
  • Large buffers are again already referenced via an IOMemoryDescriptor. You can pass this on to I/O functions, or map it into the driver's address space using CreateMapping()
1
erich On
namespace
{
    /*
    **********************************************************************************
    **          create a memory descriptor and map its address
    **********************************************************************************
    */
    IOReturn arcmsr_userclient_create_memory_descriptor_and_map_address(const void* address, size_t length, IOMemoryDescriptor** memory_descriptor) 
    {
        IOBufferMemoryDescriptor *buffer_memory_descriptor = nullptr;
        uint64_t buffer_address;
        uint64_t len;

    #if ARCMSR_DEBUG_IO_USER_CLIENT
        arcmsr_debug_print("ArcMSRUserClient: *******************************************************\n");
        arcmsr_debug_print("ArcMSRUserClient: ** IOUserClient IOMemoryDescriptor create_with_bytes   \n");
        arcmsr_debug_print("ArcMSRUserClient: *******************************************************\n");
    #endif
        if (!address || !memory_descriptor) 
        {
            return kIOReturnBadArgument;
        }
        if (IOBufferMemoryDescriptor::Create(kIOMemoryDirectionInOut, length, 0, &buffer_memory_descriptor) != kIOReturnSuccess) 
        {
            if (buffer_memory_descriptor) 
            {
                OSSafeReleaseNULL(buffer_memory_descriptor);
            }
            return kIOReturnError;
        }
        if (buffer_memory_descriptor->Map(0, 0, 0, 0, &buffer_address, &len) != kIOReturnSuccess) 
        {
            if (buffer_memory_descriptor) 
            {
                OSSafeReleaseNULL(buffer_memory_descriptor);
            }
            return kIOReturnError;
        }
        if (length != len) 
        {
            if (buffer_memory_descriptor) 
            {
                OSSafeReleaseNULL(buffer_memory_descriptor);
            }
            return kIOReturnNoMemory;
        }
        memcpy(reinterpret_cast<void*>(buffer_address), address, length);
        *memory_descriptor = buffer_memory_descriptor;
        return kIOReturnSuccess;
    }
}  /* namespace */