Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
659c3ff43a |
No files matched your search
@@ -200,7 +200,8 @@ const fatIdentityError = (
|
||||
return null;
|
||||
};
|
||||
|
||||
const parseElf = (
|
||||
/** Parse a bounded ELF header prefix without I/O. */
|
||||
export const parseElf = (
|
||||
bytes: Buffer,
|
||||
fileSize: number,
|
||||
): Result<ExecutableMetadata, string> => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
readMachoImage,
|
||||
} from "../artifacts/apple/MachoLoadCommandReader.js";
|
||||
import type { ReadAt } from "../artifacts/apple/MachoLoadCommandReader.js";
|
||||
import { parseElf, parsePeRecord } from "../domain/binaryTarget.js";
|
||||
import {
|
||||
electronFuseName,
|
||||
electronFuseState,
|
||||
@@ -33,6 +34,10 @@ export const ELECTRON_FUSE_LIMITS = {
|
||||
|
||||
const SENTINEL = Buffer.from(ELECTRON_FUSE_SENTINEL, "ascii");
|
||||
const FAT_MAGICS = new Set([0xcafebabe, 0xcafebabf, 0xbebafeca, 0xbfbafeca]);
|
||||
/** The ELF64 fixed header; it also covers the shorter ELF32 header. */
|
||||
const ELF64_HEADER_BYTES = 64;
|
||||
/** PE signature and COFF header plus the largest u16-declared optional header. */
|
||||
const PE_RECORD_BYTES = 24 + 0xffff;
|
||||
|
||||
/** Container identity and per-slice fuse wires. Offsets are file-absolute. */
|
||||
export interface ElectronFuseImage {
|
||||
@@ -214,12 +219,13 @@ const identifyContainer = async (
|
||||
if (isElf(head))
|
||||
return {
|
||||
format: "elf",
|
||||
slices: [{ index: 0, fileOffset: 0, size, architecture: null }],
|
||||
slices: wholeFile(size, await elfArchitecture(readAt, size)),
|
||||
};
|
||||
if (await isPe(readAt, size))
|
||||
const peOffset = await peHeaderOffset(readAt, size);
|
||||
if (peOffset !== null)
|
||||
return {
|
||||
format: "pe",
|
||||
slices: [{ index: 0, fileOffset: 0, size, architecture: null }],
|
||||
slices: wholeFile(size, await peArchitecture(readAt, size, peOffset)),
|
||||
};
|
||||
throw new ElectronFuseFormatFailure(
|
||||
"unsupported",
|
||||
@@ -458,11 +464,50 @@ const isFat = (head: Uint8Array): boolean => {
|
||||
return FAT_MAGICS.has(magic);
|
||||
};
|
||||
|
||||
const isPe = async (readAt: ReadAt, size: number): Promise<boolean> => {
|
||||
if (size < 64) return false;
|
||||
/**
|
||||
* The executable-header parsers own architecture; a header they cannot decode
|
||||
* leaves the architecture unknown without failing the fuse read.
|
||||
*/
|
||||
const elfArchitecture = async (
|
||||
readAt: ReadAt,
|
||||
size: number,
|
||||
): Promise<string | null> => {
|
||||
const parsed = parseElf(
|
||||
asBuffer(await readAt(0, Math.min(size, ELF64_HEADER_BYTES))),
|
||||
size,
|
||||
);
|
||||
return parsed.ok ? parsed.value.architecture : null;
|
||||
};
|
||||
|
||||
const peArchitecture = async (
|
||||
readAt: ReadAt,
|
||||
size: number,
|
||||
offset: number,
|
||||
): Promise<string | null> => {
|
||||
const parsed = parsePeRecord(
|
||||
asBuffer(await readAt(offset, Math.min(size - offset, PE_RECORD_BYTES))),
|
||||
undefined,
|
||||
);
|
||||
return parsed.ok ? parsed.value.architecture : null;
|
||||
};
|
||||
|
||||
const wholeFile = (
|
||||
size: number,
|
||||
architecture: string | null,
|
||||
): readonly SliceRange[] => [{ index: 0, fileOffset: 0, size, architecture }];
|
||||
|
||||
const asBuffer = (bytes: Uint8Array): Buffer =>
|
||||
Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
|
||||
/** Offset of a `PE\0\0` signature declared by the MZ header, or null. */
|
||||
const peHeaderOffset = async (
|
||||
readAt: ReadAt,
|
||||
size: number,
|
||||
): Promise<number | null> => {
|
||||
if (size < 64) return null;
|
||||
const header = await readAt(0, 64);
|
||||
if (header.length < 64 || header[0] !== 0x4d || header[1] !== 0x5a)
|
||||
return false;
|
||||
return null;
|
||||
const offset = Buffer.from(
|
||||
header.buffer,
|
||||
header.byteOffset,
|
||||
@@ -474,15 +519,15 @@ const isPe = async (readAt: ReadAt, size: number): Promise<boolean> => {
|
||||
offset > ELECTRON_FUSE_LIMITS.maximumPeHeaderOffset ||
|
||||
offset > size - 4
|
||||
)
|
||||
return false;
|
||||
return null;
|
||||
const signature = await readAt(offset, 4);
|
||||
return (
|
||||
signature.length === 4 &&
|
||||
return signature.length === 4 &&
|
||||
signature[0] === 0x50 &&
|
||||
signature[1] === 0x45 &&
|
||||
signature[2] === 0 &&
|
||||
signature[3] === 0
|
||||
);
|
||||
? offset
|
||||
: null;
|
||||
};
|
||||
|
||||
const isMissingPath = (cause: unknown): boolean =>
|
||||
|
||||
@@ -5,7 +5,9 @@ import { expect } from "vitest";
|
||||
|
||||
import { toolContract } from "../../src/contracts/toolContracts.js";
|
||||
import {
|
||||
elfHeaderFuseImage,
|
||||
OBSIDIAN_FUSE_BYTES,
|
||||
peHeaderFuseImage,
|
||||
sentinelFreeMacho,
|
||||
thinFuseImage,
|
||||
universalFuseImage,
|
||||
@@ -213,6 +215,53 @@ cliTest(
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.for([
|
||||
{ label: "ELF x86-64", format: "elf", machine: 62, architecture: "x86_64" },
|
||||
{ label: "ELF AArch64", format: "elf", machine: 183, architecture: "arm64" },
|
||||
{ label: "ELF unknown", format: "elf", machine: 0xfffe, architecture: null },
|
||||
{ label: "PE AMD64", format: "pe", machine: 0x8664, architecture: "x86_64" },
|
||||
{ label: "PE ARM64", format: "pe", machine: 0xaa64, architecture: "arm64" },
|
||||
{ label: "PE unknown", format: "pe", machine: 0x01f0, architecture: null },
|
||||
] as const)(
|
||||
"reports the $label header architecture beside unchanged fuse wires",
|
||||
async ({ format, machine, architecture }, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-electron-fuses-cli-arch-");
|
||||
const image =
|
||||
format === "elf"
|
||||
? elfHeaderFuseImage(machine, OBSIDIAN_FUSE_BYTES)
|
||||
: peHeaderFuseImage(machine, OBSIDIAN_FUSE_BYTES);
|
||||
const path = join(root, "electron");
|
||||
await writeFile(path, image.bytes);
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract("inspect_electron_fuses").outputSchema.parse(
|
||||
result.json,
|
||||
);
|
||||
expect(evidence.normalized_result.artifact.format).toBe(format);
|
||||
expect(evidence.subject?.architecture).toBe(architecture);
|
||||
expect(evidence.normalized_result.slices).toMatchObject([
|
||||
{
|
||||
architecture,
|
||||
sentinel_count: 1,
|
||||
sentinels: [
|
||||
{
|
||||
sentinel_offset: image.sentinelOffset,
|
||||
wire_version: 1,
|
||||
complete: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
evidence.normalized_result.slices[0]?.sentinels[0]?.fuses.map(
|
||||
(fuse) => fuse.raw_byte,
|
||||
),
|
||||
).toEqual(OBSIDIAN_FUSE_BYTES);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"reports a missing sentinel as unknown fuse state through the public CLI",
|
||||
async ({ cli }) => {
|
||||
|
||||
Vendored
+40
@@ -144,3 +144,43 @@ export const twoSentinelElf = (): {
|
||||
|
||||
export const sentinelFreeMacho = (): Buffer =>
|
||||
thinMacho(0x0100000c, Buffer.from("no fuse wire in this image"));
|
||||
|
||||
/** ELF64 little-endian header declaring `machine`, followed by one fuse wire. */
|
||||
export const elfHeaderFuseImage = (
|
||||
machine: number,
|
||||
fuses: readonly number[],
|
||||
): { readonly bytes: Buffer; readonly sentinelOffset: number } => {
|
||||
const header = Buffer.alloc(64);
|
||||
header.set([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1], 0);
|
||||
header.writeUInt16LE(3, 16);
|
||||
header.writeUInt16LE(machine, 18);
|
||||
header.writeUInt32LE(1, 20);
|
||||
header.writeUInt16LE(header.length, 52);
|
||||
return {
|
||||
bytes: Buffer.concat([header, FUSE_SENTINEL, fuseWire(1, fuses)]),
|
||||
sentinelOffset: header.length,
|
||||
};
|
||||
};
|
||||
|
||||
/** PE32+ application header declaring `machine`, followed by one fuse wire. */
|
||||
export const peHeaderFuseImage = (
|
||||
machine: number,
|
||||
fuses: readonly number[],
|
||||
): { readonly bytes: Buffer; readonly sentinelOffset: number } => {
|
||||
const dos = Buffer.alloc(0x80);
|
||||
dos.write("MZ", 0, "ascii");
|
||||
dos.writeUInt32LE(dos.length, 0x3c);
|
||||
const optional = Buffer.alloc(0xf0);
|
||||
optional.writeUInt16LE(0x20b, 0);
|
||||
optional.writeUInt32LE(16, 108);
|
||||
const coff = Buffer.alloc(24);
|
||||
coff.write("PE\0\0", 0, "binary");
|
||||
coff.writeUInt16LE(machine, 4);
|
||||
coff.writeUInt16LE(optional.length, 20);
|
||||
coff.writeUInt16LE(0x0022, 22);
|
||||
const header = Buffer.concat([dos, coff, optional]);
|
||||
return {
|
||||
bytes: Buffer.concat([header, FUSE_SENTINEL, fuseWire(1, fuses)]),
|
||||
sentinelOffset: header.length,
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user