iouc22_overview

IOUserClient2022 highlevel overview

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.

externalMethod()

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:

Additional 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.

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?

IOUserClient2022::dispatchExternalMethod()

This function simply does all the checks IOUserClient::externalMethod does, with the following additions:

  1. the selector bounds check - which means now the individual userclients do not need to

  2. 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.

Async methods

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.

Code or it didn’t happen

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 :)

Nice (compiler?) optimization

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).

Sum up

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.