fix(screenshot): reject invalid PNG header dimensions (#2472)
This commit is contained in:
parent
68ecf3df8c
commit
e74e4e951b
|
|
@ -33,3 +33,24 @@ it("rejects incomplete headers, invalid signatures and zero dimensions", () => {
|
|||
expect(readPngHeaderDimensions(zero)).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([0, 12, 14, 0xffff_ffff])("rejects IHDR data length %s", (length) => {
|
||||
const bytes = Buffer.from(png);
|
||||
bytes.writeUInt32BE(length, 8);
|
||||
expect(readPngHeaderDimensions(bytes)).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([16, 20])("rejects out-of-range PNG dimension at %s", (offset) => {
|
||||
const bytes = Buffer.from(png);
|
||||
bytes.writeUInt32BE(0x8000_0000, offset);
|
||||
expect(readPngHeaderDimensions(bytes)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("accepts the largest dimension representable by the PNG specification", () => {
|
||||
const bytes = Buffer.from(png);
|
||||
bytes.writeUInt32BE(0x7fff_ffff, 16);
|
||||
expect(readPngHeaderDimensions(bytes)).toEqual({
|
||||
width: 0x7fff_ffff,
|
||||
height: 1,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,10 +8,16 @@ export const readPngHeaderDimensions = (
|
|||
if (
|
||||
bytes.byteLength < 24 ||
|
||||
!bytes.subarray(0, 8).equals(PNG_SIGNATURE) ||
|
||||
bytes.readUInt32BE(8) !== 13 ||
|
||||
!bytes.subarray(12, 16).equals(IHDR_TYPE)
|
||||
)
|
||||
return undefined;
|
||||
const width = bytes.readUInt32BE(16);
|
||||
const height = bytes.readUInt32BE(20);
|
||||
return width === 0 || height === 0 ? undefined : { width, height };
|
||||
return width === 0 ||
|
||||
height === 0 ||
|
||||
width > 0x7fff_ffff ||
|
||||
height > 0x7fff_ffff
|
||||
? undefined
|
||||
: { width, height };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,37 @@ const validPng = Buffer.from(
|
|||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jv4sAAAAASUVORK5CYII=",
|
||||
"base64",
|
||||
);
|
||||
it.each([
|
||||
{ offset: 8, value: 12 },
|
||||
{ offset: 16, value: 0x8000_0000 },
|
||||
{ offset: 20, value: 0x8000_0000 },
|
||||
])(
|
||||
"rejects invalid IHDR metadata with a matching digest",
|
||||
async ({ offset, value }) => {
|
||||
const bytes = Buffer.from(validPng);
|
||||
bytes.writeUInt32BE(value, offset);
|
||||
const screenshot = {
|
||||
mime_type: "image/png",
|
||||
base64: bytes.toString("base64"),
|
||||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||||
width: bytes.readUInt32BE(16),
|
||||
height: bytes.readUInt32BE(20),
|
||||
};
|
||||
const result = await observeNativeUi(
|
||||
target,
|
||||
"observe_native_ui",
|
||||
{ ...scope, screenshot: true },
|
||||
{
|
||||
environment: {},
|
||||
invoke: async () => ({
|
||||
ok: true,
|
||||
result: { ...snapshot, screenshot },
|
||||
}),
|
||||
},
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
},
|
||||
);
|
||||
describe("native UI screenshot validation", () => {
|
||||
it("rejects malformed or mismatched screenshot bytes from the helper", async () => {
|
||||
const screenshot = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,37 @@ import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
|
|||
import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js";
|
||||
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
|
||||
describe("screenshot committed document identity", () => {
|
||||
it.each([
|
||||
{ offset: 8, value: 14 },
|
||||
{ offset: 16, value: 0x8000_0000 },
|
||||
{ offset: 20, value: 0x8000_0000 },
|
||||
])("rejects invalid screenshot IHDR metadata", async ({ offset, value }) => {
|
||||
const bytes = Buffer.from(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jv4sAAAAASUVORK5CYII=",
|
||||
"base64",
|
||||
);
|
||||
bytes.writeUInt32BE(value, offset);
|
||||
const browser = await startFakeCdpBrowser({
|
||||
commandResult: ({ method }) =>
|
||||
method === "Page.captureScreenshot"
|
||||
? { data: bytes.toString("base64") }
|
||||
: undefined,
|
||||
});
|
||||
onTestFinished(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
const result = await new CdpBrowserProvider().captureScreenshot(
|
||||
captureWebScreenshotInputSchema.parse({
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
target_id: "allowed-page",
|
||||
}),
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { reason: "protocol_error" },
|
||||
});
|
||||
});
|
||||
it.each(["loader-main", "loader-reloaded"])(
|
||||
"distinguishes %s for the same URL",
|
||||
async (screenshotDocumentLoader) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue