| <!-- webkit-test-runner [ IPCTestingAPIEnabled=true ] --> |
| <pre id="log"></pre> |
| <script> |
| if (window.testRunner) { |
| testRunner.dumpAsText(); |
| testRunner.waitUntilDone(); |
| } |
| const log = (s) => { document.getElementById('log').textContent += s + '\n'; }; |
| |
| // The fix rejects the forged read, cancelling the IPC reply. Depending on timing this can arrive |
| // as a rejected promise; swallow it so it does not pollute the test output. |
| window.addEventListener('unhandledrejection', (e) => { e.preventDefault(); }); |
| |
| window.setTimeout(async () => { |
| if (!window.IPC) { |
| log("PASS"); |
| return window.testRunner?.notifyDone(); |
| } |
| |
| const { CoreIPC } = await import('./coreipc.js'); |
| |
| const PF = {}; |
| for (const e of IPC.serializedEnumInfo['WebCore::PixelFormat'].valueMap) PF[e.name] = e.value; |
| const CS = {}; |
| for (const e of IPC.serializedEnumInfo['WebCore::ColorSpace'].valueMap) CS[e.name] = e.value; |
| const RP = {}; |
| for (const e of IPC.serializedEnumInfo['WebCore::RenderingPurpose'].valueMap) RP[e.name] = e.value; |
| |
| if (PF.BGRA8 === undefined || RP.LayerBacking === undefined) { |
| log("PASS"); |
| return window.testRunner?.notifyDone(); |
| } |
| |
| // Allow the bool discriminator in std::optional<CoreIPCCGColorSpace>. |
| CoreIPC.typeInfo['WebCore::PlatformColorSpace'] = [ |
| { type: 'std::optional<WebKit::CoreIPCCGColorSpace>', name: 'alias' } |
| ]; |
| const SRGB_CS = { |
| serializableColorSpace: { alias: { optionalValue: { |
| m_cgColorSpace: { alias: { variantType: 'WebCore::ColorSpace', variant: CS.SRGB } } |
| } } } |
| }; |
| |
| const W = 16, H = 16; |
| const NBYTES = W * H * 4; |
| |
| const sc = CoreIPC.newStreamConnection(); |
| const rbId = Math.floor(Math.random() * 0x1000000); |
| CoreIPC.GPU.GPUConnectionToWebProcess.CreateRenderingBackend(0, { |
| renderingBackendIdentifier: rbId, |
| connectionHandle: sc |
| }); |
| const rb = sc.newInterface('RemoteRenderingBackend', rbId); |
| try { |
| sc.connection.waitForMessage(rbId, IPC.messages.RemoteRenderingBackendProxy_DidInitialize.name, 1); |
| } catch (e) { } |
| |
| // Accelerated + LayerBacking + area <= 64*64 + BGRA8 makes isSmallLayerBacking() select |
| // ImageBufferShareableMappedIOSurfaceBitmapBackend, whose getPixelBuffer() is a no-op. |
| const bufId = Math.floor(Math.random() * 0x1000000); |
| const ctxId = Math.floor(Math.random() * 0x1000000); |
| rb.CreateImageBuffer({ |
| logicalSize: { width: W, height: H }, |
| renderingMode: 1, |
| renderingPurpose: RP.LayerBacking, |
| resolutionScale: 1.0, |
| colorSpace: SRGB_CS, |
| bufferFormat: { pixelFormat: PF.BGRA8, useLosslessCompression: 0 }, |
| identifier: bufId, |
| contextIdentifier: ctxId |
| }); |
| const ib = sc.newInterface('RemoteImageBuffer', bufId); |
| |
| // Seed WebContent-shared memory with a 0xAA sentinel, then ask the GPU process to read pixels |
| // back into it. A no-op backend leaves the returned PixelBuffer uninitialized, so the GPU |
| // process would disclose its own heap by copying it into this shared memory. The fix rejects |
| // the read for a LayerBacking backend, leaving the sentinel untouched. |
| const shm = IPC.createSharedMemory(NBYTES); |
| shm.writeBytes(new Uint8Array(NBYTES).fill(0xAA)); |
| // GetPixelBufferWithNewMemory is synchronous; when the fix rejects the read the reply is |
| // cancelled, which surfaces here as a throw. Either way the destination is in its final |
| // state once the call returns, so swallow the rejection and inspect the sentinel. |
| try { |
| ib.GetPixelBufferWithNewMemory({ |
| handle: { handle: shm, protection: 'ReadWrite' }, |
| outputFormat: { alphaFormat: 1, pixelFormat: PF.BGRA8, colorSpace: SRGB_CS }, |
| srcPoint: { x: 0, y: 0 }, |
| srcSize: { width: W, height: H } |
| }, () => {}); |
| } catch (e) { } |
| |
| setTimeout(() => { |
| const u8 = new Uint8Array(shm.readBytes(0, NBYTES)); |
| let modified = 0; |
| for (let i = 0; i < NBYTES; ++i) { |
| if (u8[i] !== 0xAA) |
| modified++; |
| } |
| if (!modified) |
| log("PASS"); |
| else |
| log(`FAIL: getPixelBuffer on a LayerBacking backend was not blocked; ${modified}/${NBYTES} destination bytes overwritten with GPU-process heap`); |
| window.testRunner?.notifyDone(); |
| }, 500); |
| }, 20); |
| </script> |