2

I'm trying to use IOConnectCallStructMethod so send data in the input field to talk to my driver in DriverKit (iPadOS)

In this particular method I send an input and I expect getting an output. But ignore the output for now.

In my case IOConnectCallStructMethod is called with Swift:

var input:[UInt8] = Array(repeating: 0, count: 200)
for i in 0..<200 {
    if (i % 2 == 0) {
        input[i] = UInt8(i)
    }
}
let arraySize:Int = input.count;
var outputSize = MemoryLayout<UInt8>.size * arraySize
var output:[UInt8] = Array(repeating: 0, count: arraySize)

let inputSize = MemoryLayout<UInt8>.size * input.count

let ret = IOConnectCallStructMethod(connection, Selector.mySelector.rawValue, &input, inputSize, &output, &outputSize)

This is the dispatch in the external method:

[ExternalMethodType_MyMethodRequest] =
{
    .function = (IOUserClientMethodFunction) &Client::StaticHandleRequest,
    .checkCompletionExists = false,
    .checkScalarInputCount = 0,
    .checkStructureInputSize = 200,
    .checkScalarOutputCount = 0,
    .checkStructureOutputSize = 200,
},

The ExternalMethod gets called and when I try to access to the input data with this:

char* input = nullptr;
size_t length = 0;
if (arguments->structureInput != nullptr)
{
    input = (char*)arguments->structureInput->getBytesNoCopy();
    length = arguments->structureInput->getLength();
    Log("Input: %s", input);
    Log("length: %zu", length);
}

If I check the variable input with a breakpoint the value is always "" and the length is 200

I tried adding the input in a struct, same result. I tried using IOConnectCallMethod, same result.

The only alternative I can do it's making the input data extra big to force the system to use the descriptor instead of the OSData, but the input in this case would be always much smaller, so not sure if the best way to go.

Interestingly if I print the output of input with:

#define Log(fmt, ...) os_log(OS_LOG_DEFAULT, "NullDriver - " fmt "\n", ##__VA_ARGS__)

It will give me private:

NullDriver - Input: <private>

Maybe the data is actually there but I can't see it?

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39
xarly
  • 2,054
  • 4
  • 24
  • 40
  • 1
    Are you sure you want to be using `%s` to log the data? That treats it as a nul-terminated string (UTF-8, usually). I don't have much experience with Swift, but it looks like you're sending arbitrary bytes, not a human readable string. In particular, your first byte is 0, so that's an empty C string. Seems like something like `%02x %02x %02x %02x` with `input[0], input[1], input[2], input[3]` would be more appropriate for your data. To fix the `` issue when using `%s`, use `%{public}s`. (but read the `os_log` documentation on the privacy ramifications of this before you ship it) – pmdj Dec 21 '22 at 13:13
  • Yeah, that's true, I didn't know, I was trying to print the full string, but I guess ( as you clarify ) it's not possible and rather I should print individual values per index. Thanks! – xarly Dec 21 '22 at 14:25
  • There is no built-in way to print an array of integers in a predefined human readable form in `os_log` or common C & C++ formatting as far as I'm aware. So this works now? – pmdj Dec 21 '22 at 15:17
  • Yes, I think it was working all the time, it's just a matter that I thought I didn't have anything – xarly Dec 21 '22 at 15:59
  • 1
    Sidenote, to log raw bytes, you might want to do `os_log(OS_LOG_DEFAULT, "%.*P", length, input);` – Ranoiaetep Dec 21 '22 at 21:55

2 Answers2

2

Further to discussion in comments:

There is no issue here with the way the data is being sent from app to dext, but rather with the way it is being logged/printed: a format string of %s is not a suitable way of printing the contents of a byte array. That treats it as a nul-terminated string (UTF-8, usually). It looks like you're sending arbitrary bytes, not a human readable string. In particular, your first byte is 0, which is how an empty C string is represented, and hence your empty output.

A format string such as "%02x %02x %02x %02x" with arguments input[0], input[1], input[2], input[3] would be more appropriate for printing the first 4 bytes; you'll have to do this in a loop, possibly writing to a buffer using snprintf before sending it to the system log, to print the entire array.

Separately: to fix the <private> issue when using %s, use %{public}s. (but read the os_log documentation on the privacy ramifications of this before you ship it)

pmdj
  • 22,018
  • 3
  • 52
  • 103
1

I think the data is there but I can't see it.

If I do:

char realInput[200];
memcpy(realInput, input, 200);

When I loop printing in the debugger: realInput[i] I get my precious data

xarly
  • 2,054
  • 4
  • 24
  • 40
  • You don't have to actually copy the data to an array. You can loop over the `input` pointer and print them directly. – Ranoiaetep Dec 21 '22 at 21:49