Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"pods": "pnpm --filter react-native-magic-toast-example exec pod-install --quiet",
"prepare": "bob build && husky",
"release": "release-it",
"test": "jest",
"test": "jest && node tools/image-size-security-check.cjs",
"test:security": "node tools/image-size-security-check.cjs",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
Expand Down
50 changes: 50 additions & 0 deletions patches/image-size@1.2.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
diff --git a/dist/types/icns.js b/dist/types/icns.js
index f2bfafef3723cb423b110304815e56e3f97f81e0..8a17039c7137b2cbb8c7a3de8c3643516c1d9c39 100644
--- a/dist/types/icns.js
+++ b/dist/types/icns.js
@@ -81,6 +81,8 @@ exports.ICNS = {
const fileLength = (0, utils_1.readUInt32BE)(input, FILE_LENGTH_OFFSET);
let imageOffset = SIZE_HEADER;
let imageHeader = readImageHeader(input, imageOffset);
+ if (imageHeader[1] < SIZE_HEADER)
+ throw new TypeError('Invalid ICNS entry length');
let imageSize = getImageSize(imageHeader[0]);
imageOffset += imageHeader[1];
if (imageOffset === fileLength)
@@ -92,6 +94,8 @@ exports.ICNS = {
};
while (imageOffset < fileLength && imageOffset < inputLength) {
imageHeader = readImageHeader(input, imageOffset);
+ if (imageHeader[1] < SIZE_HEADER)
+ throw new TypeError('Invalid ICNS entry length');
imageSize = getImageSize(imageHeader[0]);
imageOffset += imageHeader[1];
result.images.push(imageSize);
diff --git a/dist/types/utils.js b/dist/types/utils.js
index 5224bbafe87551ac415cb3de234820ccc0ff6e2c..954431e30caa980965dc38dd0cd458c6cbde37fc 100644
--- a/dist/types/utils.js
+++ b/dist/types/utils.js
@@ -49,10 +49,10 @@ function readUInt(input, bits, offset, isBigEndian) {
}
exports.readUInt = readUInt;
function readBox(input, offset) {
- if (input.length - offset < 4)
+ if (input.length - offset < 8)
return;
const boxSize = (0, exports.readUInt32BE)(input, offset);
- if (input.length - offset < boxSize)
+ if (boxSize < 8 || input.length - offset < boxSize)
return;
return {
name: (0, exports.toUTF8String)(input, 4 + offset, 8 + offset),
@@ -67,9 +67,7 @@ function findBox(input, boxName, offset) {
break;
if (box.name === boxName)
return box;
- // Fix the infinite loop by ensuring offset always increases
- // If box.size is 0, advance by at least 8 bytes (the size of the box header)
- offset += box.size > 0 ? box.size : 8;
+ offset += box.size;
}
}
exports.findBox = findBox;
38 changes: 22 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
packages:
- example

auditConfig:
# pnpm audits published versions, so it cannot see the local patch below.
# The timeout regression check is the control for both parser advisories.
ignoreGhsas:
- GHSA-5p2g-fcmc-qvqq
- GHSA-w3rx-r6r6-pgpr

# Keep newly published packages out of installs for 14 days. pnpm applies this
# to direct and transitive dependencies, including frozen-lockfile installs.
minimumReleaseAge: 20160
Expand Down Expand Up @@ -28,12 +35,20 @@ minimumReleaseAgeExclude:
- "fs-extra@11.4.0"
- "giget@3.3.1"
- "ip-address@10.3.1"
- "js-yaml@3.15.1"
- "js-yaml@4.3.1"
- "nanoid@3.3.18"
- "postcss@8.5.23"
- "release-it@21.0.0"
- "sax@1.6.1"
- "undici@7.29.0"
- "yargs@18.1.0"

patchedDependencies:
# image-size is archived and has no published fix for its zero-length entry
# loops, so Metro uses this narrow parser patch until it replaces the package.
image-size@1.2.1: patches/image-size@1.2.1.patch

# React Native's Metro bundler resolves modules by walking up node_modules
# directories, so the isolated store layout breaks it. Same setting as
# react-native-magic-modal.
Expand All @@ -51,3 +66,8 @@ overrides:
"brace-expansion@>=4.0.0 <5.0.9": "^5.0.9"
# GHSA-7p8r-x3mc-p8w7. commitlint reaches fast-uri through ajv.
"fast-uri@>=3.0.0 <3.1.5": "^3.1.5"
# GHSA-5p4m-2wfm-xmqj. Jest and Expo reach both maintained js-yaml lines.
"js-yaml@3": "^3.15.1"
"js-yaml@4": "^4.3.1"
# GHSA-2v37-7h3g-55p8. Expo reaches nanoid through PostCSS.
"nanoid@3": "^3.3.18"
67 changes: 67 additions & 0 deletions tools/image-size-security-check.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const { spawnSync } = require("node:child_process");
const path = require("node:path");

const packageRoot = path.dirname(require.resolve("image-size/package.json"));

const cases = [
{
name: "ICNS zero-length entry",
module: "icns",
handler: "ICNS",
input: [
0x69, 0x63, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x10, 0x69, 0x63, 0x30, 0x37,
0x00, 0x00, 0x00, 0x00,
],
method: "calculate",
},
{
name: "JXL zero-length box",
module: "jxl",
handler: "JXL",
input: [
0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x6a, 0x75, 0x6e, 0x6b,
],
method: "validate",
},
{
name: "HEIF zero-length box",
module: "heif",
handler: "HEIF",
input: [
0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66,
],
method: "calculate",
},
];

for (const testCase of cases) {
const script = `
const handler = require(${JSON.stringify(
path.join(packageRoot, "dist", "types", "PLACEHOLDER.js"),
)}.replace("PLACEHOLDER", ${JSON.stringify(testCase.module)}))[${JSON.stringify(
testCase.handler,
)}];
try {
handler[${JSON.stringify(testCase.method)}](Uint8Array.from(${JSON.stringify(
testCase.input,
)}));
} catch {}
`;

const result = spawnSync(process.execPath, ["-e", script], {
timeout: 1_000,
encoding: "utf8",
});

if (result.error?.code === "ETIMEDOUT") {
throw new Error(`${testCase.name} trapped the parser in an infinite loop`);
}
if (result.status !== 0) {
throw new Error(
`${testCase.name} failed unexpectedly: ${result.stderr || result.error}`,
);
}

console.log(`PASS ${testCase.name}`);
}
Loading