Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 659c3ff43a fix(electron): report ELF and PE architectures in fuse inspection
identifyContainer recognized ELF and PE executables by magic bytes only
and hardcoded a null slice architecture, so inspect-electron-fuses
reported architecture null in both the slice and the Evidence subject
for Linux and Windows Electron executables. Only the Mach-O branch
consulted a header parser.

The ELF and PE branches now take the architecture from the domain
executable-header owners (parseElf, parsePeRecord) instead of leaving it
unset. A header those parsers reject, including an unrecognized machine,
keeps the architecture null and the fuse read still succeeds. Fuse
values and container admission are unchanged.

Fixes #4786

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:32:59 +00:00
4 changed files with 146 additions and 11 deletions

No files matched your search

+2 -1
View File
@@ -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> => {
+55 -10
View File
@@ -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 =>
+49
View File
@@ -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 }) => {
+40
View File
@@ -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,
};
};