fix(native): accept walkable Mach-O command tables (#8002)

* fix(native): accept walkable Mach-O command tables

* test(native): verify large command tables through macOS CLI and MCP
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-29 14:39:20 +08:00
1 parent d82ba11ce2
commit 45cc935964
3 files changed
+81 -3

No files matched your search

+45 -1
View File
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdtemp, rm } from "node:fs/promises";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -300,6 +300,50 @@ const root = await mkdtemp(join(tmpdir(), "rea-dispatch-"));
let report;
try {
const results = [];
const commands = 4097;
const commandTable = Buffer.alloc(32 + commands * 16);
commandTable.writeUInt32LE(0xfeedfacf);
commandTable.writeUInt32LE(0x0100000c, 4);
commandTable.writeUInt32LE(2, 12);
commandTable.writeUInt32LE(commands, 16);
commandTable.writeUInt32LE(commands * 16, 20);
for (let cursor = 32; cursor < commandTable.length; cursor += 16) {
commandTable.writeUInt32LE(0x2a, cursor); // LC_SOURCE_VERSION
commandTable.writeUInt32LE(16, cursor + 4);
}
const commandTablePath = join(root, "many-commands.macho");
await writeFile(commandTablePath, commandTable);
const cli = await artifactCliEvidence(
"inspect-native-dispatch-metadata",
commandTablePath,
["--max-input-bytes", String(commandTable.length)],
);
const cliResult = nativeDispatchMetadataResultSchema.parse(
cli.normalized_result,
);
assert.deepEqual(cliResult.input_admission, {
maximum_bytes: commandTable.length,
source: "caller",
retained_bytes: commandTable.length,
});
assert.equal(
cliResult.result.target_selection.byte_length,
commandTable.length,
);
assert.deepEqual(cliResult.result.objc_classes, []);
await withArtifactMcp(commandTablePath, async (client) => {
const mcpResult = nativeDispatchMetadataResultSchema.parse(
await artifactMcpResult(client, "inspect_native_dispatch_metadata", {
max_input_bytes: commandTable.length,
}),
);
assert.deepEqual(mcpResult, cliResult);
});
results.push({
variant: "many-load-commands",
commands,
target_sha256: cliResult.target_sha256,
});
for (const mode of LINK_MODES) {
const objcPath = join(root, `fixture-${mode.mode}`);
await exec("/usr/bin/xcrun", [
+35
View File
@@ -110,6 +110,41 @@ it.each([false, true])(
},
);
it.each([
{ container: "thin", corrupt: false },
{ container: "thin", corrupt: true },
{ container: "fat", corrupt: false },
{ container: "fat", corrupt: true },
])(
"walks the entire selected command table beyond 4096 commands ($container, corrupt=$corrupt)",
({ container, corrupt }) => {
const command = Buffer.alloc(16);
command.writeUInt32LE(0x2a); // LC_SOURCE_VERSION
command.writeUInt32LE(16, 4);
const thin = machoImage({
commands: Array.from({ length: 4097 }, () => command),
});
if (corrupt)
new DataView(thin.buffer).setUint32(32 + 4096 * 16 + 4, 4, true);
const bytes = Buffer.from(
container === "fat" ? fatImage([{ cpu: CPU.arm64, bytes: thin }]) : thin,
);
const decode = () =>
decodeAppleDispatchMetadata(bytes, 10, {
path: "/many-commands",
sha256: "fixture",
});
if (corrupt) {
expect(decode).toThrow(/load command 4096 .*invalid cmdsize 4/u);
return;
}
expect(decode()).toMatchObject({
objc_classes: [],
target_selection: { container, byte_length: thin.byteLength },
});
},
);
it.each([
{ source: "__objc_const\0\nxx", expected: "__objc_const" },
{ source: "abcdefghijklmnop", expected: "abcdefghijklmnop" },
+1 -2
View File
@@ -198,9 +198,8 @@ export const parseMachoLayout = (
throw new UnsupportedMachoMetadataFormatError(
`Unsupported 32-bit Mach-O metadata for requested CPU type ${architecture}`,
);
const commands = table.commandCount;
const commandEnd = slice + table.headerSize + table.commandBytes;
if (commands > 4096 || commandEnd > sliceEnd)
if (commandEnd > sliceEnd)
throw new RangeError("Malformed Mach-O command bounds");
const state: LayoutState = {
slice,