Summary
On an iOS 18.6.2 guest paired with cloudOS 26.1, every page in Safari is blank: white, or just the page's background colour. Networking, loading, layout and JavaScript all work. The problem is compositing.
The chain:
- The GPU driver bundle that
fw preparestages from cloudOS 26.1 (AppleParavirtGPUMetalIOGPUFamily.bundle, built for iOS 26.1) fails todlopenon the iOS 18.6.2 userland. It imports_OBJC_IVAR_$_IOGPUMetalHeap._device, which iOS 18'sIOGPU.frameworkdoesn't export. - No process gets a Metal device. backboardd falls back to the CoreAnimation software renderer.
- The CA software renderer can't composite 10-bit extended-range IOSurfaces (
w30r,b3a8). - WebKit allocates all of its layer backing stores in exactly those formats, because the guest reports an extended-colour (P3) display (
HasExtendedColorDisplay = true). - Every web layer is dropped. UIKit content (8-bit BGRA) still draws, so the Safari chrome looks normal.
The iOS 26.6.2 and 27.0.1 guests (cloudOS 26.4) on the same Mac render web pages normally. See Comparison with a working guest.
Environment
| Host | MacBookPro18,1 (Apple M1 Pro), macOS 15.8.1 (24H32) |
| VPhone.bundle / Launchpad | 2.10.1 (bootChain 2.9.5, guestEnvironment 2.10.1) |
| Guest | iPhone17,3_18.6.2_22G100 + cloudOS 26.1-23B85, preset standard, variant jb, network nat |
| GPU driver installed in guest | AppleParavirtGPUMetalIOGPUFamily.bundle from 26.1-23B85: CFBundleVersion 63.2.2, DTPlatformVersion 26.1, MinimumOSVersion 26.1, LC_BUILD_VERSION minos 26.1 sdk 26.1, binary SHA-256 754717559817ae7e31411f04cd59d6ba549aeba71f196025dba22ab891b5e4f3 |
Steps to reproduce
- Create an iOS 18.6.2 (22G100) + cloudOS 26.1 (23B85) VM with the
standardpreset and boot it. - Open Safari on any site (
https://example.com,https://www.google.com,https://www.wikipedia.org).
Expected: the page renders. Actual: the content area stays white. The URL bar shows the site, the reload button is shown (the load finished), and no crash report is written.
Root cause
1. The Metal driver doesn't load (every process, from boot)
From backboardd at boot (same error in SpringBoard, SBRendererService, cameracaptured, mediaplaybackd, MobileSafari, com.apple.WebKit.GPU, PosterBoard extensions, nebulad…):
backboardd[67] [com.apple.CFBundle:loading] Error loading /System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle/AppleParavirtGPUMetalIOGPUFamily (99):
dlopen(…, 0x0109): Symbol not found: _OBJC_IVAR_$_IOGPUMetalHeap._device
Referenced from: <5ABC78D1-6760-32D4-B29A-3F6A77231E73> /System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle/AppleParavirtGPUMetalIOGPUFamily (built for iOS 26.1 which is newer than running OS)
Expected in: <44BC1A1D-96BD-30E8-A70D-4FFDDA7811B3> /System/Library/PrivateFrameworks/IOGPU.framework/IOGPU
backboardd[67] [com.apple.coreanimation:WindowServer] CoreAnimation: Software renderer! Simulating crash!
backboardd[67] [com.apple.coreanimation:WindowServer] CoreAnimation: Failed to initialize Metal or OpenGLES contexts!In an app on the guest, MTLCreateSystemDefaultDevice() returns nil and MTLCopyAllDevices() returns an empty array.
2. It's a single missing export
I resolved every IOGPU/Metal import of the driver against the guest's frameworks (Frida, Module.findExportByName after loading IOGPU.framework):
- 26.1 driver: 89 imports checked, only
_OBJC_IVAR_$_IOGPUMetalHeap._deviceis missing. - 26.4 driver (
26.4-23E5207q, 64.4.4): 102 imports checked, the same single symbol is missing. Swapping in the newer driver would not help.
The ivar itself exists on iOS 18.6.2. Only its offset symbol isn't exported:
IOGPUMetalHeap ivars (iOS 18.6.2 22G100): _resource@0x38, _device@0x40, _allocator@0x48, _mutex@0x70, _size@0xb0In the 26.1 driver the symbol has one GOT slot (__got 0x583B0) and two uses, both reading heap->_device:
0xd050 adrp x8, 0x58000
0xd054 ldr x8, [x8, #0x3b0] ; _OBJC_IVAR_$_IOGPUMetalHeap._device
0xd058 ldrsw x8, [x8]
0xd05c ldr x0, [x20, x8]
0xd180 adrp x8, 0x58000
0xd184 ldr x8, [x8, #0x3b0] ; _OBJC_IVAR_$_IOGPUMetalHeap._device
0xd188 ldrsw x8, [x8]
0xd18c ldr x0, [x21, x8]3. The software renderer drops WebKit's 10-bit backing stores
With CA in software mode, backboardd logs this whenever WebKit content (or a snapshot of it) has to be composited:
backboardd[67] [com.apple.coreanimation:OGL] SW renderer invalid destination format w30r
backboardd[67] [com.apple.coreanimation:OGL] SW renderer invalid destination format b3a8w30r / b3a8 are WebKit's PixelFormat::RGB10 / RGB10A8. RemoteLayerBackingStore picks them when screenSupportsExtendedColor() is true, which comes from MobileGestalt HasExtendedColorDisplay (Aixt/MEN2O2B7f+8m4TxUA). It is 1 on this guest (iPhone 16 profile, ArtworkDisplayGamut = P3).
Comparison with a working guest on the same host
Same host, same bundle (2.10.1), both standard, same probe in each guest:
| iOS 18.6.2 (22G100) + cloudOS 26.1 | iOS 27.0.1 (24A446) + cloudOS 26.4 | |
|---|---|---|
| GPU driver in guest | 26.1 (63.2.2, minos 26.1) | 26.4 (64.4.4, minos 26.4) |
Driver dlopen | fails: _OBJC_IVAR_$_IOGPUMetalHeap._device | loads |
MTLCreateSystemDefaultDevice() | nil | Apple Paravirtual device GPU (Apple3, Common3) |
| backboardd | Software renderer! / Failed to initialize Metal or OpenGLES contexts! | no renderer errors |
MGGetBoolAnswer("HasExtendedColorDisplay") | 1 | 1 |
| Safari on wikipedia.org | blank | renders |
Both guests report an extended-colour display, so WebKit picks the 10-bit backing-store formats on both. Only the guest whose driver loads (and so composites on Metal) can draw them. The userland/driver mismatch is the difference, not the host and not WebKit.
What I ruled out
- Network:
curlfrom the guest gets 200 from google.com, wikipedia.org and apple.com over IPv4. WebKit's log showsdidReceiveResponse (httpStatusCode=200),didFinishResourceLoad (length=122197),didCommitLoadForFrame,didFinishLoadForFrame, and-[WKWebView _didCommitLayerTree:]commits arriving in the UI process. - DOM / JS: on wikipedia.org the search field gets focus. The caret and the form-assist bar show over the blank page.
- Site-specific or JIT:
example.comis blank too. So is a typeddata:URL with no script:data:text/html,<body style=background:red><div style=background:blue;height:300px;font-size:60px>BLUE</div><div style=background:lime;height:300px;transform:translateZ(0)>GREEN</div><h1>RED</h1>. Only the red page background shows (it reaches the UI process as a plain layerbackgroundColor). The painted blue block, the composited green layer and all the text are missing. - GPU-process DOM rendering: turning
UseGPUProcessForDOMRenderingEnabledoff changes the white to the page background colour, but painted content is still missing. Both paths produce IOSurface contents in the same formats. - WebKit crashes: none. No WebContent/GPU/Networking crash reports, and the processes stay alive.
Confirmation
Spawning Safari under Frida with MGGetBoolAnswer("HasExtendedColorDisplay") and WebCore::screenSupportsExtendedColor() forced to return false makes WebKit use 8-bit BGRA backing stores. With nothing else changed, example.com, google.com (consent dialog) and wikipedia.org all render correctly on the same guest. Script: safari-no-extended-color.js below.
Suggested fixes
- Make the 26.x driver load on an 18.x userland. The ABI gap at bind time is one ivar-offset symbol. On an iOS 18 base,
cfw installcould patch the twoldr x8,[x8,#GOT]; ldrsw x8,[x8]sites to a constant offset ofIOGPUMetalHeap._device(0x40 on 22G100, best derived from the guest's own IOGPU ObjC metadata, not hard-coded), and drop or weak-import the bind. The tooling is already there (retargetGPUBundleToTVOS,MachOBuildPlatform, theheap_index_offsetdriver patch). After that, Metal on 18.x needs a runtime check for other behavioural differences between a 26.x driver and the 18.x Metal/IOGPU frameworks. - Guardrail.
VPhonePCCGPUDriver.validateBundleonly comparesDTPlatformVersionwith the cloudOS version. It never checks the driver'sMinimumOSVersion(26.1) against the iPhone userland (18.6.2).fw prepare/cfw install/vm infoshould warn that the guest will run without Metal and with CoreAnimation in software mode. - Mitigation while Metal is unavailable. When the guest has no Metal device, make WebKit clients use 8-bit backing stores. For example, a SystemHook hook that returns
falseforMGGetBoolAnswer("HasExtendedColorDisplay")in WebKit client processes. That restores Safari, SFSafariViewController and every in-app WKWebView. - Docs.
compatibility.mdstill lists17,3_18.6.2_22G100 + 26.1-23B85under earlier combinations. It should note that this pairing currently has no GPU acceleration and blank web content.
Workaround script
Keep the Frida session open while browsing (--eternalize didn't keep the hooks in my test):
frida -U -f com.apple.mobilesafari -l safari-no-extended-color.jsconst extendedColorKeys = ["HasExtendedColorDisplay", "Aixt/MEN2O2B7f+8m4TxUA"];
const mobileGestalt = Process.getModuleByName("libMobileGestalt.dylib");
Interceptor.attach(mobileGestalt.getExportByName("MGGetBoolAnswer"), {
onEnter(args) {
this.key = args[0].isNull() ? null : new ObjC.Object(args[0]).toString();
},
onLeave(result) {
if (extendedColorKeys.includes(this.key) && result.toInt32() !== 0) {
result.replace(0);
}
},
});
function hookWebCore() {
const webCore = Process.findModuleByName("WebCore");
if (webCore === null) {
return false;
}
const screenSupportsExtendedColor = webCore.findExportByName("_ZN7WebCore27screenSupportsExtendedColorEPNS_6WidgetE");
if (screenSupportsExtendedColor !== null) {
Interceptor.attach(screenSupportsExtendedColor, { onLeave(result) { result.replace(0); } });
}
return true;
}
if (!hookWebCore()) {
const timer = setInterval(() => { if (hookWebCore()) clearInterval(timer); }, 5);
}Notes
- The guest also has a RootHide bootstrap (SSL Kill Switch 3 is injected into MobileSafari but reports "Hook disabled"). It isn't involved: the driver load fails in backboardd at boot.
- I only tested 18.6.2 (22G100). Every iOS 18.x userland probably hits the same missing export with any cloudOS 26.x driver.
- The same host's iOS 26.6.2 guest also renders Safari normally (reported by the VM's owner). I took the measurements in the comparison table on the iOS 27.0.1 guest.
Activity
0No activity yet.