mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 14:26:07 +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
+51
-12
@@ -35,28 +35,39 @@ export const parseExecutableHeader = (
|
||||
bytes.length >= 4 &&
|
||||
bytes.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))
|
||||
)
|
||||
return parseElf(bytes);
|
||||
return parseElf(bytes, fileSize);
|
||||
if (bytes.length >= 2 && bytes[0] === 0x4d && bytes[1] === 0x5a)
|
||||
return parseMz(bytes, fileSize);
|
||||
if (bytes.length < 8) return err("truncated or unsupported binary header");
|
||||
const magic = bytes.readUInt32BE(0);
|
||||
if ([0xfeedface, 0xfeedfacf, 0xcefaedfe, 0xcffaedfe].includes(magic))
|
||||
return parseThinMachO(bytes, magic);
|
||||
return parseThinMachO(bytes, magic, fileSize);
|
||||
if ([0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca].includes(magic))
|
||||
return parseFatMachO(bytes, magic, hostArchitecture);
|
||||
return parseFatMachO(bytes, magic, hostArchitecture, fileSize);
|
||||
return err("unsupported binary format");
|
||||
};
|
||||
|
||||
const parseThinMachO = (
|
||||
bytes: Buffer,
|
||||
magic: number,
|
||||
fileSize: number,
|
||||
): Result<ExecutableMetadata, string> => {
|
||||
if (bytes.length < 8) return err("truncated Mach-O header");
|
||||
const is64 = magic === 0xfeedfacf || magic === 0xcffaedfe;
|
||||
const headerSize = is64 ? 32 : 28;
|
||||
if (bytes.length < headerSize)
|
||||
return err(
|
||||
`truncated Mach-O header: a ${is64 ? 64 : 32}-bit header needs ${headerSize} bytes; the file has ${fileSize}`,
|
||||
);
|
||||
const little = magic === 0xcefaedfe || magic === 0xcffaedfe;
|
||||
const architecture = machArchitecture(
|
||||
little ? bytes.readUInt32LE(4) : bytes.readUInt32BE(4),
|
||||
);
|
||||
const read = (offset: number): number =>
|
||||
little ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
|
||||
const architecture = machArchitecture(read(4));
|
||||
if (architecture === undefined) return err("unsupported Mach-O architecture");
|
||||
const commandBytes = read(20);
|
||||
if (headerSize + commandBytes > fileSize)
|
||||
return err(
|
||||
`truncated Mach-O load commands: the header declares ${commandBytes} bytes after its ${headerSize}-byte header; the file has ${fileSize}`,
|
||||
);
|
||||
return ok({
|
||||
format: "mach-o",
|
||||
architecture,
|
||||
@@ -68,6 +79,7 @@ const parseFatMachO = (
|
||||
bytes: Buffer,
|
||||
magic: number,
|
||||
host: NodeJS.Architecture,
|
||||
fileSize: number,
|
||||
): Result<ExecutableMetadata, string> => {
|
||||
const little = magic === 0xbebafeca || magic === 0xbfbafeca;
|
||||
const is64 = magic === 0xcafebabf || magic === 0xbfbafeca;
|
||||
@@ -79,10 +91,24 @@ const parseFatMachO = (
|
||||
if (count === 0 || count > 128 || bytes.length < 8 + count * entrySize)
|
||||
return err("truncated or invalid FAT architecture table");
|
||||
const architectures: BinaryArchitecture[] = [];
|
||||
const sliceEnds = new Map<BinaryArchitecture, bigint>();
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const architecture = machArchitecture(read(8 + index * entrySize));
|
||||
if (architecture !== undefined && !architectures.includes(architecture))
|
||||
architectures.push(architecture);
|
||||
const entry = 8 + index * entrySize;
|
||||
const architecture = machArchitecture(read(entry));
|
||||
if (architecture === undefined || architectures.includes(architecture))
|
||||
continue;
|
||||
architectures.push(architecture);
|
||||
const offset = is64
|
||||
? little
|
||||
? bytes.readBigUInt64LE(entry + 8)
|
||||
: bytes.readBigUInt64BE(entry + 8)
|
||||
: BigInt(read(entry + 8));
|
||||
const size = is64
|
||||
? little
|
||||
? bytes.readBigUInt64LE(entry + 16)
|
||||
: bytes.readBigUInt64BE(entry + 16)
|
||||
: BigInt(read(entry + 12));
|
||||
sliceEnds.set(architecture, offset + size);
|
||||
}
|
||||
const preferred =
|
||||
host === "arm64"
|
||||
@@ -96,6 +122,11 @@ const parseFatMachO = (
|
||||
: undefined;
|
||||
if (preferred === undefined || !architectures.includes(preferred))
|
||||
return err(`FAT binary has no host-compatible ${host} architecture`);
|
||||
const sliceEnd = sliceEnds.get(preferred) ?? 0n;
|
||||
if (sliceEnd > BigInt(fileSize))
|
||||
return err(
|
||||
`truncated FAT slice: the ${preferred} slice ends at byte ${sliceEnd}; the file has ${fileSize}`,
|
||||
);
|
||||
return ok({
|
||||
format: "mach-o",
|
||||
architecture: preferred,
|
||||
@@ -103,11 +134,19 @@ const parseFatMachO = (
|
||||
});
|
||||
};
|
||||
|
||||
const parseElf = (bytes: Buffer): Result<ExecutableMetadata, string> => {
|
||||
if (bytes.length < 20) return err("truncated ELF header");
|
||||
const parseElf = (
|
||||
bytes: Buffer,
|
||||
fileSize: number,
|
||||
): Result<ExecutableMetadata, string> => {
|
||||
if (bytes.length < 6) return err("truncated ELF header");
|
||||
if (bytes[4] !== 1 && bytes[4] !== 2) return err("unsupported ELF class");
|
||||
const little = bytes[5] === 1;
|
||||
if (!little && bytes[5] !== 2) return err("unsupported ELF endianness");
|
||||
const headerSize = bytes[4] === 1 ? 52 : 64;
|
||||
if (bytes.length < headerSize)
|
||||
return err(
|
||||
`truncated ELF header: an ELF${bytes[4] === 1 ? 32 : 64} header needs ${headerSize} bytes; the file has ${fileSize}`,
|
||||
);
|
||||
const machine = little ? bytes.readUInt16LE(18) : bytes.readUInt16BE(18);
|
||||
const architecture = elfArchitecture(machine);
|
||||
if (architecture === undefined) return err("unsupported ELF architecture");
|
||||
|
||||
Reference in new issue
Block a user