blob: c74aa848dc3a82eb93e7a1aaa0cba4fb0b22856f [file] [edit]
<!DOCTYPE html><!-- webkit-test-runner [ IPCTestingAPIEnabled=true ] -->
<p>This test passes if an FEComponentTransfer with an empty Table/Discrete tableValues sent over IPC with FilterRenderingMode::Accelerated does not cause the CoreImage Metal kernel to read past the bounds of its table buffer.</p>
<pre id="log"></pre>
<script>
testRunner?.dumpAsText();
testRunner?.waitUntilDone();
let finished = false;
function done(s) {
if (finished)
return;
finished = true;
document.getElementById('log').textContent = s;
testRunner?.notifyDone();
}
let nextID = 100000; function genID() { return nextID++; }
setTimeout(async () => {
if (!window.IPC)
return done('PASS');
try {
const { CoreIPC } = await import('./coreipc.js');
// std::optional<CoreIPCCGColorSpace> needs its bool discriminator exposed.
CoreIPC.typeInfo['WebCore::PlatformColorSpace'] = [
{ type: 'std::optional<WebKit::CoreIPCCGColorSpace>', name: 'alias' }
];
// Look the ColorSpace value up by name rather than hardcoding an ordinal.
const CS = {};
for (const e of IPC.serializedEnumInfo['WebCore::ColorSpace'].valueMap) CS[e.name] = e.value;
const sRGB = { serializableColorSpace: { alias: { optionalValue: {
m_cgColorSpace: { alias: { variantType: 'WebCore::ColorSpace', variant: CS.SRGB } } } } } };
const sc = CoreIPC.newStreamConnection();
const RBID = genID();
CoreIPC.GPU.GPUConnectionToWebProcess.CreateRenderingBackend(0, {
renderingBackendIdentifier: RBID,
connectionHandle: sc
});
const rrb = sc.newInterface('RemoteRenderingBackend', RBID);
function createImageBuffer(id, ctx) {
rrb.CreateImageBuffer({
logicalSize: { width: 32, height: 32 }, renderingMode: 1, renderingPurpose: 0,
resolutionScale: 1.0, colorSpace: sRGB,
bufferFormat: { pixelFormat: 2, useLosslessCompression: 0 },
identifier: id, contextIdentifier: ctx,
});
// Drain the unsolicited DidCreateBackend in-turn; otherwise it reaches the stream
// connection's dummy receiver, which ASSERT_NOT_REACHED()s in debug builds.
sc.connection.waitForMessage(id, IPC.messages.RemoteImageBufferProxy_DidCreateBackend.name);
}
// Source buffer, filled opaque white.
const SRC = genID(), SRCCTX = genID();
createImageBuffer(SRC, SRCCTX);
sc.newInterface('RemoteGraphicsContext', SRCCTX).FillRectWithColor({
rect: { location: { x: 0, y: 0 }, size: { width: 32, height: 32 } },
color: { data: { optionalValue: { isSemantic: false, usesFunctionSerialization: false,
data: { variantType: 'WebCore::PackedColor::RGBA', variant: { value: 0xFFFFFFFF } } } } }
});
sc.newInterface('RemoteImageBuffer', SRC).FlushContext({});
function ctf(type, table) {
return { type: type, slope: 0, intercept: 0, amplitude: 0, exponent: 0, offset: 0, tableValues: table };
}
const sourceGraphic = { subclasses: { variantType: 'WebCore::SourceGraphic', variant: { operatingColorSpace: sRGB } } };
const identity = ctf(1, []);
const sentinel = 0.37;
// Destination buffer: draw SRC through an FEComponentTransfer whose red channel has a
// one-entry DISCRETE table (the sentinel) and whose green channel has an *empty* DISCRETE
// table. Before the fix the CoreImage kernel indexed tableStart[-1] for the empty channel,
// leaking the adjacent (red) table entry into green.
const DST = genID(), DSTCTX = genID();
createImageBuffer(DST, DSTCTX);
sc.newInterface('RemoteGraphicsContext', DSTCTX).DrawFilteredImageBuffer({
sourceImageIdentifier: { optionalValue: SRC },
sourceImageRect: { location: { x: 0, y: 0 }, size: { width: 32, height: 32 } },
filter: { subclasses: { variantType: 'WebCore::SVGFilterRenderer', variant: {
primitiveUnits: 1,
expression: { alias: [{ index: 0, level: 1, geometry: {} }, { index: 1, level: 0, geometry: {} }] },
effects: [sourceGraphic,
{ subclasses: { variantType: 'WebCore::FEComponentTransfer', variant: {
redFunction: ctf(3, [sentinel]), greenFunction: ctf(3, []),
blueFunction: identity, alphaFunction: identity,
operatingColorSpace: sRGB } } }],
geometry: { referenceBox: { location: { x: 0, y: 0 }, size: { width: 32, height: 32 } },
filterRegion: { location: { x: 0, y: 0 }, size: { width: 32, height: 32 } },
scale: { width: 1, height: 1 } },
filterRenderingModes: 3, renderingOptions: 0,
renderingResourceIdentifierIfExists: {},
} } },
});
sc.newInterface('RemoteImageBuffer', DST).FlushContext({});
// Read the filtered result back synchronously into shared memory.
const N = 32 * 32 * 4;
const shm = IPC.createSharedMemory(N);
shm.writeBytes(new Uint8Array(N).fill(0xAA));
sc.newInterface('RemoteImageBuffer', DST).GetPixelBufferWithNewMemory({
handle: { protection: 'ReadWrite', handle: shm },
outputFormat: { alphaFormat: 0, pixelFormat: 0, colorSpace: sRGB },
srcPoint: { x: 0, y: 0 }, srcSize: { width: 32, height: 32 },
});
const px = new Uint8Array(shm.readBytes(0, N));
const green = px[(8 * 32 + 8) * 4 + 1];
const sentinelByte = Math.round(sentinel * 255);
sc.connection.invalidate();
if (Math.abs(green - sentinelByte) <= 4)
done('FAIL: empty-table DISCRETE channel returned the adjacent table entry (green=' + green + '); kernel indexed tableStart[-1]');
else
done('PASS');
} catch (e) {
done('PASS');
}
}, 0);
setTimeout(() => done('PASS'), 25000);
</script>