ios16_restricted_iouserclients

iOS 16 - restricted Userclients

On the first day of WWDC22 Apple released a very exciting beta version of iOS16. I was waiting for that, and indeed, Apple exceeded all expectations here. Folks on Twitter already covered many of the awesome new security features, hardenings, and attack surface reduction, so I wouldn’t repeat that here. I do encourage you to check that out. For your convenience, I wrote here a (very) partial list:

And in general, this Twitter thread is amazing.

The thing I wanted to cover here is something new I noticed in IOSurfaceRootUserClient and IOGPUDeviceUserClient.

Attack surface reduction in IOSurface and IOGPU

As I’ve said before, I do not like to repeat highly documented and common knowledge stuff. Therefore I won’t explain here what IOKit is, how it was designed and how it works. There are many (many) resources on that, which I linked in a previous blogpost. That blogpost also covers the high-level of what happens when an EL0 process interacts with a kernel driver through its userclient.

If you’ll take a look at IOSurfaceRootUserClient, you’ll noticed the IOSurfaceRootUserClient::sMethodDescs table is now duplicated, and now we actually have two tables, one after the other - the first one is IOSurfaceRootUserClient::sMethodDescs, and the second one is IOSurfaceRootUserClient::sMethodDescsRestricted. Same thing goes for IOGPUDeviceUserClient - we have IOGPUDeviceUserClient::sDeviceMethods, followed by IOGPUDeviceUserClient::sDeviceMethodsRestricted.

Interesting. The difference between these tables (the “regular” and the “restricted”) is that some of the methods in the “restricted” versions are replaced with the function s_restricted:

And, no surprises there, both of the functions are identical:

FFFFFFF0097B6A00 IOSurfaceRootUserClient::s_restricted(IOSurfaceRootUserClient*, void *, IOExternalMethodArguments *)
FFFFFFF0097B6A00                 MOV             W0, #0xE00002E2
FFFFFFF0097B6A08                 RET
FFFFFFF00952B4E0 IOGPUDeviceUserClient::s_restricted(IOGPUDeviceUserClient*, void *, IOExternalMethodArguments *)
FFFFFFF00952B4E0                 MOV             W0, #0xE00002E2
FFFFFFF00952B4E8                 RET

As a reminder - 0xE00002E2 is kIOReturnNotPermitted.

Well, I guess we can assume what’s going on here (don’t worry, we will reverse it, because assuming stuff is the root of all evil). It seems the approach is “OK, so some processes need access to certain functionalities of IOSurfaceRoot or IOGPUDevice, but they don’t need access to all the functionalities. Let’s give each entity exactly what it needs”.

It’s not the only way to achieve such attack surface reduction, and it’s not the first time Apple has done something like this. You could simply add new entitlements and check at the beginning of certain external methods handlers if the required entitlement is set. Tielei noted it has happened before in IOMFB, and actually, iOS 16 added another function to IOMFB - IOMobileFramebufferUserClient::s_create_gain_map, and it’s gated in this way by the entitlement "com.apple.private.gain-map-access".

However, it’s cool to see this generic change, of simply creating “reduce exposed” versions of this kernel components.

Implementation

First of all, some details:

Now, let’s see how the externalMethod function chooses which table to use. I expect to see new entitlement involved, and it would be nice to be familiar with them. The relevant code is very straightforward. Let’s check it out.

IOGPUDeviceUserClient

FFFFFFF00952B97C                 ADRL            X8, IOGPUDeviceUserClient::sDeviceMethodsRestricted
FFFFFFF00952B984                 LDRB            W9, [X0,#0x103]
FFFFFFF00952B988 ; sizeof(IOExternalMethodDispatch)
FFFFFFF00952B988                 MOV             W10, #0x18
FFFFFFF00952B98C ; set X8 to the function from sDeviceMethodsRestricted
FFFFFFF00952B98C                 UMADDL          X8, W1, W10, X8
FFFFFFF00952B990                 ADRL            X11, IOGPUDeviceUserClient::sDeviceMethods
FFFFFFF00952B998 ; set X10 to the function from sDeviceMethods
FFFFFFF00952B998                 UMADDL          X10, W1, W10, X11
FFFFFFF00952B99C ; choose between X10 and X8 based on offset 0x103 from the userclient object
FFFFFFF00952B99C                 CMP             W9, #0
FFFFFFF00952B9A0                 CSEL            X3, X10, X8, EQ ; dispatch

By looking for this value, we can easily see the following code from IOGPUDeviceUserClient::deviceUserClientStart:

FFFFFFF00952B5F4                 BL              IOGPUDevice::isRestrictedClient(void)
FFFFFFF00952B5F8                 STRB            W0, [X19,#0x103]

(while X19 is this). Ok, let’s see what IOGPUDevice::isRestrictedClient does:

FFFFFFF009540E8C IOGPUDevice::isRestrictedClient(void)
FFFFFFF009540E8C                 ADRL            X1, aComApplePrivat_72 ; "com.apple.private.gpu-restricted"
FFFFFFF009540E94                 B               IOGPUDevice::doesEntitlementExist(char const*)

Great! So, now we know that:

IOSurfaceRootUserClient

Same steps - check out the following code from IOSurfaceRootUserClient::externalMethod:

FFFFFFF0097BA1D0 IOSurfaceRootUserClient::externalMethod(unsigned int, IOExternalMethodArgumentsOpaque *)
FFFFFFF0097BA1D0                                         ; DATA XREF: com.apple.iokit.IOSurface:__const:FFFFFFF00A0831C8↓o
FFFFFFF0097BA1D0                 ADRL            X8, IOSurfaceRootUserClient::sMethodDescsRestricted
FFFFFFF0097BA1D8                 LDRB            W9, [X0,#0x11F]
FFFFFFF0097BA1DC                 ADRL            X10, IOSurfaceRootUserClient::sMethodDescs
FFFFFFF0097BA1E4                 CMP             W9, #0
FFFFFFF0097BA1E8 ; select between IOSurfaceRootUserClient::sMethodDescsRestricted and IOSurfaceRootUserClient::sMethodDescs
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 *)

Searching for this offset (0x11F), we get to the following code from IOSurfaceRootUserClient::init:

FFFFFFF0097B6A58                 ADRL            X2, aComApplePrivat_79 ; "com.apple.private.iosurfaceinfo"
FFFFFFF0097B6A60                 MOV             X1, X20 ; task
FFFFFFF0097B6A64                 BL              IOSurfaceRootUserClient::taskHasEntitlement(task *,char const*)
FFFFFFF0097B6A68 ; has iosurfaceinfo entitlemment
FFFFFFF0097B6A68                 STRB            W0, [X19,#0x11A]
FFFFFFF0097B6A6C                 ADRL            X2, aComApplePrivat_80 ; "com.apple.private.gpu-restricted"
FFFFFFF0097B6A74                 MOV             X1, X20 ; task
FFFFFFF0097B6A78                 BL              IOSurfaceRootUserClient::taskHasEntitlement(task *,char const*)
FFFFFFF0097B6A7C ; has gpu-restricted entitlement
FFFFFFF0097B6A7C                 STRB            W0, [X19,#0x11F]
FFFFFFF0097B6A80                 ADRL            X2, aComApplePrivat_81 ; "com.apple.private.IOSurface.protected-a"...
FFFFFFF0097B6A88                 MOV             X1, X20 ; task
FFFFFFF0097B6A8C                 BL              IOSurfaceRootUserClient::taskHasEntitlement(task *,char const*)
FFFFFFF0097B6A90 ; has IOSurface.protected-access entitlement
FFFFFFF0097B6A90                 STRB            WZR, [X19,#0x11B]

Awesome, now we know some of the new entitlements:

IOUserClient2022

You probably noticed that IOSurfaceRootUserClient::externalMethod calls IOUserClient2022::dispatchExternalMethod (it basically wraps that now). There are more userclients that behave this way - their externalMethod function simply calls to IOUserClient2022::dispatchExternalMethod, with the relevant external method table and arguments. This is also new, and it was added in the beta version of iOS 16 from June. But let’s leave this for another time.

Applications

Let’s see if some interesting processes are more restricted using these new userclients. Let’s look at com.apple.WebKit.WebContent in the beta version:

saaramar@Saars-Air com.apple.WebKit.WebContent.xpc % codesign -dvv --entitlements - ./com.apple.WebKit.WebContent       
Executable=/Users/saaramar/Documents/projects/restricted_userclient_blogpost/com.apple.WebKit.WebContent.xpc/com.apple.WebKit.WebContent
Identifier=com.apple.WebKit.WebContent
Format=bundle with Mach-O thin (arm64e)
CodeDirectory v=20400 size=756 flags=0x2(adhoc) hashes=13+7 location=embedded
Signature=adhoc
Info.plist entries=29
TeamIdentifier=not set
Sealed Resources version=2 rules=9 files=0
Internal requirements count=0 size=12
[Dict]
	[Key] dynamic-codesigning
	[Value]
		[Bool] true
	[Key] com.apple.private.memorystatus
	[Value]
		[Bool] true
	[Key] com.apple.private.verified-jit
	[Value]
		[Bool] true
	[Key] com.apple.private.pac.exception
	[Value]
		[Bool] true
	[Key] com.apple.QuartzCore.secure-mode
	[Value]
		[Bool] true
	[Key] com.apple.private.gpu-restricted
	[Value]
		[Bool] true
...

Indeed! WebContent now has the "com.apple.private.gpu-restricted" entitlement, which means it can’t get access to all the IOGPUDevice external method. Another binary is com.apple.WebKit.GPU.

In addition, it seems that /usr/libexec/backboardd has the "com.apple.private.IOSurface.protected-access" entitlement.

Sum up

Apple has done a fantastic work on attack surface reductions. It’s not the first time flows to certain functionalities in the kernel are being gated by entitlements, and it probably won’t be the last.

I hope you enjoyed reading this short write-up.

Thanks,

Saar Amar