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)
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:
IOConnectCall…Method()
s so they arrive asIOMemoryDescriptor
s 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.IOMemoryDescriptor
into its space usingIOConnectMapMemory64()
/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 yourbuffer
, 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:
IOConnectCallStructMethod(connection, selector, buffer, 256, NULL, NULL);
in your app. This meansbuffer
is passed as a "struct input" argument to your external method.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 theIOConnectCallStructMethod()
call.structureInput
/structureInputSize
fields in the incomingIOExternalMethodArguments
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.IOMemoryDescriptor
. One way to do this is indeed viaIOMemoryDescriptor::CreateMapping()
.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.
IOUserClientMethodArguments
'structureInput
field, which points to anOSData
object. You can access the content via thegetBytesNoCopy()
/getLength()
methods.IOMemoryDescriptor
for onward I/O, the only way I know of is to create anIOBufferMemoryDescriptor
using eitherIOUSBHostDevice::CreateIOBuffer()
orIOBufferMemoryDescriptor::Create
and then copying the data from theOSData
object into the buffer.IOMemoryDescriptor
. You can pass this on to I/O functions, or map it into the driver's address space usingCreateMapping()