As I mentioned in a previous blogpost (and was also briefly mentioned on Twitter as well), the iOS 16 beta version shipped in WWDC22 contains something new called IOUserClient2022
. The purpose of this short write-up is to share some details about how it works.
One of the most interesting things about IOUserClient
is of course the externalMethod
function:
externalMethod(uint32_t selector, IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch = NULL,
OSObject *target = NULL, void *reference = NULL);
The general functionality of the externalMethod
implementation over different userclients is very similar. It’s basically a wrapper for IOUserClient::externalMethod
, and it’s pretty simple: usually, it does bounds check on the selector
, and then calls IOUserClient::externalMethod
, which does:
IOExternalMethodDispatch
/IOExternalAsyncMethod
from the external methods table of the classAdditional checks (for instance, entitlements) are implemented in the specific external methods’ handlers.
You can see the I/O checks in the following code in XNU:
IOReturn
IOUserClient::externalMethod( uint32_t selector, IOExternalMethodArguments * args,
IOExternalMethodDispatch * dispatch, OSObject * target, void * reference )
{
IOReturn err;
IOService * object;
IOByteCount structureOutputSize;
if (dispatch) {
uint32_t count;
count = dispatch->checkScalarInputCount;
if ((kIOUCVariableStructureSize != count) && (count != args->scalarInputCount)) {
return kIOReturnBadArgument;
}
count = dispatch->checkStructureInputSize;
if ((kIOUCVariableStructureSize != count)
&& (count != ((args->structureInputDescriptor)
? args->structureInputDescriptor->getLength() : args->structureInputSize))) {
return kIOReturnBadArgument;
}
count = dispatch->checkScalarOutputCount;
if ((kIOUCVariableStructureSize != count) && (count != args->scalarOutputCount)) {
return kIOReturnBadArgument;
}
count = dispatch->checkStructureOutputSize;
if ((kIOUCVariableStructureSize != count)
&& (count != ((args->structureOutputDescriptor)
? args->structureOutputDescriptor->getLength() : args->structureOutputSize))) {
return kIOReturnBadArgument;
}
if (dispatch->function) {
err = (*dispatch->function)(target, reference, args);
} else {
err = kIOReturnNoCompletion; /* implementator can dispatch */
}
return err;
}
...
Now, let’s see what changed in IOUserClient2022
.
First of all, IOUserClient2022
is kind enough to implement the externalMethod
function. And it’s very simple:
FFFFFFF007EA6FF4 IOUserClient2022::externalMethod(unsigned int, IOExternalMethodArguments *, IOExternalMethodDispatch *, OSObject *, void *)
FFFFFFF007EA6FF4
FFFFFFF007EA6FF4 var_10= -0x10
FFFFFFF007EA6FF4 var_s0= 0
FFFFFFF007EA6FF4
FFFFFFF007EA6FF4 PACIBSP
FFFFFFF007EA6FF8 SUB SP, SP, #0x20
FFFFFFF007EA6FFC STP X29, X30, [SP,#0x10+var_s0]
FFFFFFF007EA7000 ADD X29, SP, #0x10
FFFFFFF007EA7004 ADRL X8, aIouserclientCp ; "IOUserClient.cpp"
FFFFFFF007EA700C MOV W9, #0x18DB
FFFFFFF007EA7010 STP X8, X9, [SP,#0x10+var_10]
FFFFFFF007EA7014 ; string: wrong externalMethod for IOUserClient2022 @%s:%d
FFFFFFF007EA7014 ADRL X0, aWrongExternalm ; "wrong externalMethod for IOUserClient20"...
FFFFFFF007EA701C BL panic
Ok, nice. Apple deprecated externalMethod
in IOUserClient2022
. If something tries to call it, it will panic.
Let’s check out some userclient that was converted to use IOUserClient2022
. Not all userclients moved to use IOUserClient2022
, but some did. As I mentioned in my previous blogpost, IOSurfaceRootUserClient
is one of them. Let’s view IOSurfaceRootUserClient::externalMethod
again:
FFFFFFF0097BA1D0 IOSurfaceRootUserClient::externalMethod(unsigned int, IOExternalMethodArgumentsOpaque *)
FFFFFFF0097BA1D0 ADRL X8, IOSurfaceRootUserClient::sMethodDescsRestricted
FFFFFFF0097BA1D8 LDRB W9, [X0,#0x11F]
FFFFFFF0097BA1DC ADRL X10, IOSurfaceRootUserClient::sMethodDescs
FFFFFFF0097BA1E4 CMP W9, #0
FFFFFFF0097BA1E8 CSEL X3, X10, X8, EQ
FFFFFFF0097BA1EC MOV W4, #0x35 ; '5'
FFFFFFF0097BA1F0 MOV X5, X0
FFFFFFF0097BA1F4 MOV X6, #0
FFFFFFF0097BA1F8 B IOUserClient2022::dispatchExternalMethod(uint,IOExternalMethodArgumentsOpaque *,IOExternalMethodDispatch2022 const*,ulong,OSObject *,void *)
Ok, so now the main function is IOUserClient2022::dispatchExternalMethod
. Let’s dive into that function and see what it does. If you want to see how userclients (and which) use IOUserClient2022
, simply xref IOUserClient2022::dispatchExternalMethod
. All these userclients simply wrap the call to IOUserClient2022::dispatchExternalMethod
in their externalMethod
function. And, by viewing these call-sites, we know right away what the arguments are:
X0 - this
W1 - selector
X2 - pointer to the arguments structure (IOExternalMethodArgumentsOpaque *arguments)
X3 - external method table pointer (IOExternalMethodDispatch2022 *dispatch)
W4 - number of external methods in the table
etc...
You can see these arguments make sense in the IOSurfaceRootUserClient::externalMethod
above.
So, what does IOUserClient2022::dispatchExternalMethod
do?
This function simply does all the checks IOUserClient::externalMethod
does, with the following additions:
the selector
bounds check - which means now the individual userclients do not need to
an entitlement check - the new structure that describes each entry in the external methods table (IOExternalMethodDispatch2022
) has a pointer to an entitlement string, which is being used as follows:
FFFFFFF007EB28F0 LDR X1, [X8,#IOExternalMethodDispatch2022_s.pEntitlement] ; __s
FFFFFFF007EB28F4 ; if entitlement string pointer != NULL,
FFFFFFF007EB28F4 ; check if the task has that entitlement
FFFFFFF007EB28F4 CBZ X1, call_function
FFFFFFF007EB28F8 MOV X0, #0 ; int
FFFFFFF007EB28FC BL _IOTaskHasEntitlement
FFFFFFF007EB2900 CBZ W0, return_kIOReturnNotPrivileged
Cool. I couldn’t see any userclient that uses IOUserClient2022
and uses this feature yet, but it’s there.
As you know, external methods could be sync or async. In the async case, if the external method takes an async wake mach port argument then the lifetime of the reference on that mach port passed to the external method will be managed by MIG semantics. For more info/links regarding lifetime management paradigms in MIG, see this.
Well, IOUserClient2022::dispatchExternalMethod
has the following code:
FFFFFFF007EB28D0 check_async_wake_port
FFFFFFF007EB28D0 LDR X8, [X22,#IOExternalMethodArgumentsOpaque_s.asyncWakePort]
FFFFFFF007EB28D4 CBZ X8, check_entitlement
FFFFFFF007EB28D8 MOV W8, #0x28 ; '('
FFFFFFF007EB28DC ; X24 - the selector
FFFFFFF007EB28DC ; X8 - sizeof(IOExternalMethodDispatch2022_s)
FFFFFFF007EB28DC ; X23 - external methods table
FFFFFFF007EB28DC MADD X8, X24, X8, X23
FFFFFFF007EB28E0 LDRB W8, [X8,#0x18]
FFFFFFF007EB28E4 CBZ W8, common_error_return
FFFFFFF007EB28E8
FFFFFFF007EB28E8 check_entitlement
FFFFFFF007EB28E8 MOV W8, #0x28 ; '('
FFFFFFF007EB28EC MADD X8, X24, X8, X23
FFFFFFF007EB28F0 LDR X1, [X8,#IOExternalMethodDispatch2022_s.pEntitlement] ; __s
FFFFFFF007EB28F4 ; if entitlement string pointer != NULL,
FFFFFFF007EB28F4 ; check if the task has that entitlement
FFFFFFF007EB28F4 CBZ X1, call_function
FFFFFFF007EB28F8 MOV X0, #0 ; int
FFFFFFF007EB28FC BL _IOTaskHasEntitlement
FFFFFFF007EB2900 CBZ W0, return_kIOReturnNotPrivileged
FFFFFFF007EB2904
FFFFFFF007EB2904 call_function
...
IOUserClient2022::dispatchExternalMethod
checks if the asyncWakePort
==MACH_PORT_NULL
, and if it’s not, it validates that offset 0x18 of our new IOExternalMethodDispatch2022
is not 0. To be more precise, it fetches only one byte from the 8-byte field at offset 0x18, and compares it to 0. If it is 0 the function returns kIOReturnBadArgument
. In other words, if asyncWakePort
!=MACH_PORT_NULL
, field 0x18 has to be != 0.
Let’s take one example for method that runs asynchronously - IOSurfaceRootUserClient::s_set_surface_notify
:
FFFFFFF00A083510 DCQ IOSurfaceRootUserClient::s_set_surface_notify(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)
FFFFFFF00A083518 DCD 0
FFFFFFF00A08351C DCD 0x18
FFFFFFF00A083520 DCD 0
FFFFFFF00A083524 DCD 0
FFFFFFF00A083528 DCQ 1
FFFFFFF00A083530 DCQ 0
Indeed, offset 0x18 here is 1. You can see more examples for such methods in the kernelcache (many examples in IOHIDLibUserClient
).
In all the places I looked, offset 0x18 was 0 or 1. And because the function only uses it to test != 0, I assume it’s a boolean used to mark external methods as sync/async.
I’m calling this code out because async methods are pretty interesting. Asynchronous methods are a good place for races (as an example, see async_wake). In general, async functionality always brings fun-related issues (lifetime ownership, refcounting bugs, inconsistency with MIG semantics, etc.).
Now, we can see the new IOExternalMethodDispatch2022
structure:
00000000 IOExternalMethodDispatch2022_s struc ; (sizeof=0x28, mappedto_6476)
00000000 function DCQ ?
00000008 checkScalarInputCount DCD ?
0000000C checkStructureInputSize DCD ?
00000010 checkScalarOutputCount DCD ?
00000014 checkStructureOutputSize DCD ?
00000018 isAsyncMethod DCQ ?
00000020 pEntitlement DCQ ?
00000028 IOExternalMethodDispatch2022_s ends
And yes, offset 0x18 is just one byte, but because of alignment reasons, the compiler adds a padding of 7 bytes.
Below is the whole function, with my comments and structures’ fields. Please note that the 3rd argument is IOExternalMethodArgumentsOpaque*
and not IOExternalMethodArguments*
; however, it seems all the relevant fields are in the same order. Makes sense, I expect it makes adoption easier.
FFFFFFF007EB277C IOUserClient2022::dispatchExternalMethod(unsigned int, IOExternalMethodArgumentsOpaque *, IOExternalMethodDispatch2022 const*, unsigned long, OSObject *, void *)
FFFFFFF007EB277C
FFFFFFF007EB277C var_40 = -0x40
FFFFFFF007EB277C var_30 = -0x30
FFFFFFF007EB277C var_20 = -0x20
FFFFFFF007EB277C var_10 = -0x10
FFFFFFF007EB277C var_s0 = 0
FFFFFFF007EB277C
FFFFFFF007EB277C PACIBSP
FFFFFFF007EB2780 STP X26, X25, [SP,#-0x10+var_40]!
FFFFFFF007EB2784 STP X24, X23, [SP,#0x40+var_30]
FFFFFFF007EB2788 STP X22, X21, [SP,#0x40+var_20]
FFFFFFF007EB278C STP X20, X19, [SP,#0x40+var_10]
FFFFFFF007EB2790 STP X29, X30, [SP,#0x40+var_s0]
FFFFFFF007EB2794 ADD X29, SP, #0x40
FFFFFFF007EB2798 ; kIOReturnBadArgument==0xE00002C2
FFFFFFF007EB2798 MOV W19, #0xE00002C2
FFFFFFF007EB27A0 ; X3 is the extermal methods table,
FFFFFFF007EB27A0 ; it can't be NULL
FFFFFFF007EB27A0 CBZ X3, return_kIOReturnError
FFFFFFF007EB27A4 ; Through this function, X24 is the selector;
FFFFFFF007EB27A4 ;
FFFFFFF007EB27A4 ; X4 is the argument that specifies the
FFFFFFF007EB27A4 ; number of external methods in the table.
FFFFFFF007EB27A4 ; Check that the selector is in bounds
FFFFFFF007EB27A4 MOV W24, W1
FFFFFFF007EB27A8 CMP X24, X4
FFFFFFF007EB27AC B.CS common_error_return
FFFFFFF007EB27B0 MOV X20, X6
FFFFFFF007EB27B4 MOV X21, X5
FFFFFFF007EB27B8 MOV X23, X3
FFFFFFF007EB27BC MOV X22, X2
FFFFFFF007EB27C0 MOV W8, #0x28 ; '('
FFFFFFF007EB27C4 ; 0x28==sizeof(IOExternalMethodDispatch2022_s),
FFFFFFF007EB27C4 ; which is the size of each entry in the
FFFFFFF007EB27C4 ; external methods table. The MADD is
FFFFFFF007EB27C4 ; simply access the table at a certain index:
FFFFFFF007EB27C4 ; table + idx * sizeof(IOExternalMethodDispatch2022_s)
FFFFFFF007EB27C4 MADD X8, X24, X8, X3
FFFFFFF007EB27C8 LDR W8, [X8,#IOExternalMethodDispatch2022_s.checkScalarInputCount]
FFFFFFF007EB27CC ; if count == kIOUCVariableStructureSize (0xffffffff),
FFFFFFF007EB27CC ; we don't need to compare it to args->scalarInputCount
FFFFFFF007EB27CC CMN W8, #1
FFFFFFF007EB27D0 B.EQ check_StructureInputSize
FFFFFFF007EB27D4 LDR W9, [X22,#IOExternalMethodArgumentsOpaque_s.scalarInputCount]
FFFFFFF007EB27D8 CMP W8, W9
FFFFFFF007EB27DC B.NE common_error_return
FFFFFFF007EB27E0
FFFFFFF007EB27E0 check_StructureInputSize
FFFFFFF007EB27E0 MOV W8, #0x28 ; '('
FFFFFFF007EB27E4 MADD X8, X24, X8, X23
FFFFFFF007EB27E8 LDR W25, [X8,#IOExternalMethodDispatch2022_s.checkStructureInputSize]
FFFFFFF007EB27EC CMN W25, #1
FFFFFFF007EB27F0 B.EQ check_scalarOutputCount
FFFFFFF007EB27F4 LDR X0, [X22,#IOExternalMethodArgumentsOpaque_s.structureInputDescriptor]
FFFFFFF007EB27F8 CBZ X0, check_structureInputSize
FFFFFFF007EB27FC LDR X16, [X0]
FFFFFFF007EB2800 MOV X17, X0
FFFFFFF007EB2804 MOVK X17, #0xCDA1,LSL#48
FFFFFFF007EB2808 AUTDA X16, X17
FFFFFFF007EB280C LDR X8, [X16,#0xB0]!
FFFFFFF007EB2810 MOV X9, X16
FFFFFFF007EB2814 MOV X17, X9
FFFFFFF007EB2818 MOVK X17, #0xB392,LSL#48
FFFFFFF007EB281C ; calls args->structureInputDescriptor->getLength()
FFFFFFF007EB281C BLRAA X8, X17
FFFFFFF007EB2820 CMP X0, X25
FFFFFFF007EB2824 B.NE common_error_return
FFFFFFF007EB2828 B check_scalarOutputCount
FFFFFFF007EB282C ; ---------------------------------------------------------------------------
FFFFFFF007EB282C
FFFFFFF007EB282C return_kIOReturnError
FFFFFFF007EB282C SUB W19, W19, #6
FFFFFFF007EB2830
FFFFFFF007EB2830 common_error_return
FFFFFFF007EB2830 MOV X0, X19
FFFFFFF007EB2834 LDP X29, X30, [SP,#0x40+var_s0]
FFFFFFF007EB2838 LDP X20, X19, [SP,#0x40+var_10]
FFFFFFF007EB283C LDP X22, X21, [SP,#0x40+var_20]
FFFFFFF007EB2840 LDP X24, X23, [SP,#0x40+var_30]
FFFFFFF007EB2844 LDP X26, X25, [SP+0x40+var_40],#0x50
FFFFFFF007EB2848 RETAB
FFFFFFF007EB284C ; ---------------------------------------------------------------------------
FFFFFFF007EB284C
FFFFFFF007EB284C check_structureInputSize
FFFFFFF007EB284C LDR W0, [X22,#IOExternalMethodArgumentsOpaque_s.structureInputSize]
FFFFFFF007EB2850 CMP X0, X25
FFFFFFF007EB2854 B.NE common_error_return
FFFFFFF007EB2858
FFFFFFF007EB2858 check_scalarOutputCount
FFFFFFF007EB2858 MOV W8, #0x28 ; '('
FFFFFFF007EB285C MADD X8, X24, X8, X23
FFFFFFF007EB2860 LDR W8, [X8,#IOExternalMethodDispatch2022_s.checkScalarOutputCount]
FFFFFFF007EB2864 CMN W8, #1
FFFFFFF007EB2868 B.EQ loc_FFFFFFF007EB2878
FFFFFFF007EB286C LDR W9, [X22,#IOExternalMethodArgumentsOpaque_s.scalarOutputCount]
FFFFFFF007EB2870 CMP W8, W9
FFFFFFF007EB2874 B.NE common_error_return
FFFFFFF007EB2878
FFFFFFF007EB2878 loc_FFFFFFF007EB2878
FFFFFFF007EB2878 MOV W8, #0x28 ; '('
FFFFFFF007EB287C MADD X8, X24, X8, X23
FFFFFFF007EB2880 LDR W25, [X8,#IOExternalMethodDispatch2022_s.checkStructureOutputSize]
FFFFFFF007EB2884 CMN W25, #1
FFFFFFF007EB2888 B.EQ check_async_wake_port
FFFFFFF007EB288C LDR X0, [X22,#IOExternalMethodArgumentsOpaque_s.structureOutputDescriptor]
FFFFFFF007EB2890 CBZ X0, check_structureOutputSize
FFFFFFF007EB2894 LDR X16, [X0]
FFFFFFF007EB2898 MOV X17, X0
FFFFFFF007EB289C MOVK X17, #0xCDA1,LSL#48
FFFFFFF007EB28A0 AUTDA X16, X17
FFFFFFF007EB28A4 LDR X8, [X16,#0xB0]!
FFFFFFF007EB28A8 MOV X9, X16
FFFFFFF007EB28AC MOV X17, X9
FFFFFFF007EB28B0 MOVK X17, #0xB392,LSL#48
FFFFFFF007EB28B4 ; calls args->structureOutputDescriptor->getLength()
FFFFFFF007EB28B4 BLRAA X8, X17
FFFFFFF007EB28B8 CMP X0, X25
FFFFFFF007EB28BC B.NE common_error_return
FFFFFFF007EB28C0 B check_async_wake_port
FFFFFFF007EB28C4 ; ---------------------------------------------------------------------------
FFFFFFF007EB28C4
FFFFFFF007EB28C4 check_structureOutputSize
FFFFFFF007EB28C4 LDR W0, [X22,#IOExternalMethodArgumentsOpaque_s.structureOutputSize]
FFFFFFF007EB28C8 CMP X0, X25
FFFFFFF007EB28CC B.NE common_error_return
FFFFFFF007EB28D0
FFFFFFF007EB28D0 check_async_wake_port
FFFFFFF007EB28D0 LDR X8, [X22,#IOExternalMethodArgumentsOpaque_s.asyncWakePort]
FFFFFFF007EB28D4 CBZ X8, check_entitlement
FFFFFFF007EB28D8 MOV W8, #0x28 ; '('
FFFFFFF007EB28DC ; X24 - the selector
FFFFFFF007EB28DC ; X8 - sizeof(IOExternalMethodDispatch2022_s)
FFFFFFF007EB28DC ; X23 - external methods table
FFFFFFF007EB28DC MADD X8, X24, X8, X23
FFFFFFF007EB28E0 LDRB W8, [X8,#IOExternalMethodDispatch2022_s.isAsyncMethod]
FFFFFFF007EB28E4 CBZ W8, common_error_return
FFFFFFF007EB28E8
FFFFFFF007EB28E8 check_entitlement
FFFFFFF007EB28E8 MOV W8, #0x28 ; '('
FFFFFFF007EB28EC MADD X8, X24, X8, X23
FFFFFFF007EB28F0 LDR X1, [X8,#IOExternalMethodDispatch2022_s.pEntitlement] ; __s
FFFFFFF007EB28F4 ; if entitlement string pointer != NULL,
FFFFFFF007EB28F4 ; check if the task has that entitlement
FFFFFFF007EB28F4 CBZ X1, call_function
FFFFFFF007EB28F8 MOV X0, #0 ; int
FFFFFFF007EB28FC BL _IOTaskHasEntitlement
FFFFFFF007EB2900 CBZ W0, return_kIOReturnNotPrivileged
FFFFFFF007EB2904
FFFFFFF007EB2904 call_function
FFFFFFF007EB2904 MOV W8, #0x28 ; '('
FFFFFFF007EB2908 MUL X8, X24, X8
FFFFFFF007EB290C ; fetch the function, check if it's != NULL
FFFFFFF007EB290C LDR X3, [X23,X8]
FFFFFFF007EB2910 CBZ X3, return_kIOReturnNoCompletion
FFFFFFF007EB2914 MOV X0, X21
FFFFFFF007EB2918 MOV X1, X20
FFFFFFF007EB291C MOV X2, X22
FFFFFFF007EB2920 LDP X29, X30, [SP,#0x40+var_s0]
FFFFFFF007EB2924 LDP X20, X19, [SP,#0x40+var_10]
FFFFFFF007EB2928 LDP X22, X21, [SP,#0x40+var_20]
FFFFFFF007EB292C LDP X24, X23, [SP,#0x40+var_30]
FFFFFFF007EB2930 LDP X26, X25, [SP+0x40+var_40],#0x50
FFFFFFF007EB2934 AUTIBSP
FFFFFFF007EB2938 MOV X16, #0xBCAD
FFFFFFF007EB293C ; Finally!
FFFFFFF007EB293C ; Actually calling the external method's handler.
FFFFFFF007EB293C ; Note that this is BRAA and not BLRAA, which means
FFFFFFF007EB293C ; the method will return to the caller, with X0
FFFFFFF007EB293C ; as the handler's return value
FFFFFFF007EB293C ;
FFFFFFF007EB293C ; Because of that, we restore the registers from
FFFFFFF007EB293C ; the stack before
FFFFFFF007EB293C BRAA X3, X16
FFFFFFF007EB2940 ; ---------------------------------------------------------------------------
FFFFFFF007EB2940
FFFFFFF007EB2940 return_kIOReturnNoCompletion
FFFFFFF007EB2940 ADD W19, W19, #0x28 ; '('
FFFFFFF007EB2944 B common_error_return
FFFFFFF007EB2948 ; ---------------------------------------------------------------------------
FFFFFFF007EB2948
FFFFFFF007EB2948 return_kIOReturnNotPrivileged
FFFFFFF007EB2948 SUB W19, W19, #1
FFFFFFF007EB294C B common_error_return
FFFFFFF007EB294C ; End of function IOUserClient2022::dispatchExternalMethod(uint,IOExternalMethodArgumentsOpaque *,IOExternalMethodDispatch2022 const*,ulong,OSObject *,void *)
That’s it :)
It’s not new, but if we are already here - please note the nice optimization regarding the return value of the IOUserClient2022::dispatchExternalMethod
. The very same optimization exists in older kernelcaches (for instance, in IOUserClient::externalMethod
). The optimization works as follows: the function sets some register (in this case, w19
) to 0xE00002C2
(kIOReturnBadArgument
). In the common_error_return
flow, X0
is set to this register value as the return value, and the other error flows just do add/sub in order to change it to different error values. For instance, in order to return kIOReturnNotPrivileged
, which is 0xE00002C1
, the function does:
FFFFFFF007EB2948 return_kIOReturnNotPrivileged
FFFFFFF007EB2948 SUB W19, W19, #1
FFFFFFF007EB294C B common_error_return
In order to return kIOReturnNoCompletion
, which is 0xE00002EA
, the function does:
FFFFFFF007EB2940 return_kIOReturnNoCompletion
FFFFFFF007EB2940 ADD W19, W19, #0x28 ; '('
FFFFFFF007EB2944 B common_error_return
And in order to return kIOReturnError
, which is 0xE00002BC
, the function jumps to:
FFFFFFF007EB282C return_kIOReturnError
FFFFFFF007EB282C SUB W19, W19, #6
FFFFFFF007EB2830
FFFFFFF007EB2830 common_error_return
FFFFFFF007EB2830 MOV X0, X19
FFFFFFF007EB2834 LDP X29, X30, [SP,#0x40+var_s0]
FFFFFFF007EB2838 LDP X20, X19, [SP,#0x40+var_10]
FFFFFFF007EB283C LDP X22, X21, [SP,#0x40+var_20]
FFFFFFF007EB2840 LDP X24, X23, [SP,#0x40+var_30]
FFFFFFF007EB2844 LDP X26, X25, [SP+0x40+var_40],#0x50
FFFFFFF007EB2848 RETAB
I have no idea if that’s intentional by the engineer who worked on it or simply a standard compiler optimization. Either way, it’s cute (the alternative would be to encode the 32-bit immediate in the instruction, which would take 8 bytes instead of 4 bytes).
It’s important to note that IOUserClient2022
is clearly still under development. There are many indicators for that:
In addition, I won’t be surprised if there will be more interesting improvements/features in this area. Let’s wait for future kernelcaches and see.
I hope you enjoyed this write-up.
Thanks,
Saar Amar.