mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 05:46:17 +00:00
fix(native): refuse truncated executable headers at target admission
Target admission accepted a thin Mach-O after reading only its magic and CPU type, so a 12-byte prefix opened as a valid arm64 executable and the first provider query failed as a generic execution failure. ELF headers were likewise accepted after 20 bytes, and a FAT file whose selected slice lies beyond its end was admitted. Require the complete fixed header (Mach-O 28 or 32 bytes, ELF 52 or 64), Mach-O load commands that fit in the file, and a selected FAT slice within the file, checking commitments against the file size rather than the probed prefix. Each refusal names the failed constraint. Fixes #1189 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1afbc1914
commit
0b04d384cc
6 files changed
+137
-20
No files matched your search
@@ -90,3 +90,52 @@ describe("executable header parsing", () => {
|
||||
expect(parseExecutableHeader(truncatedDirectories, "x64").ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("executable header completeness", () => {
|
||||
const reason = (bytes: Buffer, fileSize = bytes.length) => {
|
||||
const result = parseExecutableHeader(bytes, "arm64", fileSize);
|
||||
return result.ok ? null : result.error;
|
||||
};
|
||||
|
||||
it.each([
|
||||
[
|
||||
thinMach(0xcffaedfe, 0x0100000c).subarray(0, 12),
|
||||
"a 64-bit header needs 32 bytes",
|
||||
],
|
||||
[
|
||||
thinMach(0xcefaedfe, 0x01000007).subarray(0, 27),
|
||||
"a 32-bit header needs 28 bytes",
|
||||
],
|
||||
[elf(1, 1, 62).subarray(0, 51), "an ELF32 header needs 52 bytes"],
|
||||
[elf(2, 1, 183).subarray(0, 63), "an ELF64 header needs 64 bytes"],
|
||||
])(
|
||||
"rejects a header shorter than its fixed size (%#)",
|
||||
(bytes, constraint) => {
|
||||
expect(reason(bytes)).toContain(constraint);
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects Mach-O load commands that extend past the file", () => {
|
||||
const header = thinMach(0xcffaedfe, 0x0100000c);
|
||||
header.writeUInt32LE(100, 20);
|
||||
|
||||
expect(reason(header)).toBe(
|
||||
"truncated Mach-O load commands: the header declares 100 bytes after its 32-byte header; the file has 32",
|
||||
);
|
||||
// The resolver probes a prefix; commitments are checked against the file.
|
||||
expect(reason(header, 132)).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects a FAT file whose host slice extends past the file", () => {
|
||||
const bytes = fat([0x01000007, 0x0100000c]);
|
||||
bytes.writeUInt32BE(4096, 8 + 20 + 8);
|
||||
bytes.writeUInt32BE(64, 8 + 20 + 12);
|
||||
|
||||
expect(reason(bytes, 4100)).toBe(
|
||||
"truncated FAT slice: the arm64 slice ends at byte 4160; the file has 4100",
|
||||
);
|
||||
expect(reason(bytes, 4160)).toBeNull();
|
||||
// Only the slice REA would analyze must be present.
|
||||
expect(parseExecutableHeader(bytes, "x64", 4100).ok).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user