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:
1 parent
d82ba11ce2
commit
45cc935964
3 files changed
+81
-3
No files matched your search
@@ -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", [
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user