Files
rea/src/native/metadata/NativeSectionDirectory.ts
T
morluto 599099f656 refactor(pe): share optional-header structural facts
Decode optional-header declarations once and pass detached directory words, locations and SizeOfHeaders to native consumers. Preserve executable machine admission, raw GPU/resource machine reporting, RVA mapping and certificate file-offset semantics.

Validation: fresh build/catalog, full static checks, 466 core/provider fixture tests, 122 workflow tests and 14 selected file-backed PE admission tests passed. Public CLI and actual stdio MCP used authored artifacts; installed external providers were not qualified. Added 17 structural cases and two GPU machine-policy controls. Eager directory decoding costs remain unmeasured.
2026-10-25 05:28:31 +08:00

360 lines
12 KiB
TypeScript

import { readElfTableDeclarations } from "../../domain/elfDeclaredRanges.js";
import { readPeOptionalHeaderStructure } from "../../domain/peOptionalHeader.js";
import {
BinaryFormatFailure,
BinaryReader,
BinaryResourceFailure,
declaredFileRange,
type ElfDynamicSegment,
} from "./BinaryContainer.js";
import { decodeElfSectionNameTable } from "./ElfSectionNameTable.js";
/** A borrowed positional source. Drivers own admission, cancellation and file identity. */
export interface NativeReadRequest {
readonly offset: number;
readonly size: number;
}
export interface NativeSection {
readonly index: number;
readonly name: string | null;
readonly name_state: "known" | "unavailable" | "truncated";
readonly header_offset: number;
readonly offset: bigint;
readonly size: bigint;
readonly address: bigint;
readonly type: number;
readonly flags: bigint;
readonly link: number;
readonly info: number;
readonly entry_size: bigint;
readonly file_backed: boolean;
}
/** Raw relevant program declarations; consumers own load/dynamic admission. */
export type ElfProgramDeclaration =
| {
readonly type: 1;
readonly flags: number;
readonly file_offset: bigint;
readonly file_size: bigint;
readonly virtual_address: bigint;
readonly memory_size: bigint;
}
| ({ readonly type: 2 } & ElfDynamicSegment);
export interface NativeSectionDirectory {
readonly format: "elf" | "pe";
readonly bits: 32 | 64;
readonly little: boolean;
readonly machine: number;
readonly elf_os_abi: number | null;
readonly flags: number;
readonly section_names_limitation?: string;
readonly programs: readonly ElfProgramDeclaration[];
readonly sections: readonly NativeSection[];
}
const STRUCTURE_BYTES = 16 * 1024 * 1024;
const reject = (message: string): never => {
throw new BinaryFormatFailure("malformed", message);
};
const exact = (value: bigint): number => {
if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER))
reject("Native offset exceeds exact host precision");
return Number(value);
};
const request = (
fileSize: number,
offset: bigint,
size: bigint,
): NativeReadRequest => {
if (
offset < 0n ||
size < 0n ||
offset > BigInt(fileSize) ||
size > BigInt(fileSize) - offset
)
reject("Native directory range is truncated or outside the file");
if (size > BigInt(STRUCTURE_BYTES))
throw new BinaryResourceFailure(
"structure",
STRUCTURE_BYTES,
"Native directory exceeds the 16777216-byte (16 MiB) structural decoding budget. This release has no runtime override.",
);
return { offset: exact(offset), size: exact(size) };
};
/** One ELF interpretation for in-memory images and bounded descriptor-backed inspections. */
export function* readElfSectionDirectory(
fileSize: number,
): Generator<NativeReadRequest, NativeSectionDirectory, Buffer> {
const prefix = yield request(fileSize, 0n, 16n);
const headerSize = prefix[4] === 1 ? 52 : 64;
const bytes = yield request(fileSize, 0n, BigInt(headerSize));
const parsed = yield* readElfTableDeclarations(bytes, fileSize);
if (!parsed.ok) return reject(parsed.error);
const { header, programs, sections: sectionTable } = parsed.value;
const { elfClass: bits, byteOrder, machine } = header;
const little = byteOrder === "little";
const wide = bits === 64;
const r = new BinaryReader(bytes, little);
const {
offset: program_offset,
width: program_width,
count: program_count,
} = programs;
const {
offset: sectionOffset,
width: sectionWidth,
count,
namesIndex,
} = sectionTable;
const programDeclarations: ElfProgramDeclaration[] = [];
if (program_count !== 0) {
const programs = new BinaryReader(
yield request(
fileSize,
program_offset,
BigInt(program_count) * BigInt(program_width),
),
little,
);
// At most one record per table entry, bounded before allocation by the
// existing 16 MiB table allowance and minimum 32/56-byte ELF record width.
for (let index = 0; index < program_count; index++) {
const at = index * program_width;
const type = programs.u32(at);
if (type === 1)
programDeclarations.push({
type,
flags: programs.u32(at + (wide ? 4 : 24)),
file_offset: programs.word(at + (wide ? 8 : 4), bits),
virtual_address: programs.word(at + (wide ? 16 : 8), bits),
file_size: programs.word(at + (wide ? 32 : 16), bits),
memory_size: programs.word(at + (wide ? 40 : 20), bits),
});
else if (type === 2)
programDeclarations.push({
type,
offset: programs.word(at + (wide ? 8 : 4), bits),
size: programs.word(at + (wide ? 32 : 16), bits),
});
}
}
const sections: NativeSection[] = [];
let section_names_limitation: string | undefined;
if (count !== 0) {
const table = new BinaryReader(
yield request(
fileSize,
sectionOffset,
BigInt(count) * BigInt(sectionWidth),
),
little,
);
let names: Buffer | null = null;
if (namesIndex !== 0) {
const h = namesIndex * sectionWidth;
if (table.u32(h + 4) !== 3)
reject("ELF section name table is not a string table");
const rawNames = yield request(
fileSize,
table.word(h + (wide ? 24 : 16), bits),
table.word(h + (wide ? 32 : 20), bits),
);
const decoded = decodeElfSectionNameTable(
rawNames,
table.word(h + 8, bits),
bits,
little,
Math.max(0, STRUCTURE_BYTES - rawNames.length),
);
if (decoded.state === "malformed") return reject(decoded.message);
if (decoded.state === "limit")
throw new BinaryResourceFailure(
"structure",
STRUCTURE_BYTES,
`Decoded ELF section names require ${String(decoded.requiredBytes)} bytes, above the ${String(decoded.maximumBytes)}-byte remaining structural budget`,
);
if (decoded.state === "unsupported")
section_names_limitation = `ELF section names are unavailable because compression type ${String(decoded.compressionType)} is unsupported`;
else names = decoded.bytes;
} else if (count > 1) {
section_names_limitation =
"ELF section names are unavailable because no section-name string table is declared";
}
const decodedNames = new Map<
number,
{ name: string | null; state: NativeSection["name_state"] }
>();
let nameBytes = 0;
for (let index = 0; index < count; index++) {
const h = index * sectionWidth;
const nameOffset = table.u32(h);
let name: string | null = null;
let name_state: NativeSection["name_state"] = "unavailable";
if (names !== null) {
if (nameOffset >= names.length) {
section_names_limitation ??=
"ELF section names are incomplete because a name offset is outside its string table";
} else {
let decoded = decodedNames.get(nameOffset);
if (decoded === undefined) {
const window = names.subarray(
nameOffset,
Math.min(nameOffset + 65536, names.length),
);
const end = window.indexOf(0);
nameBytes += end < 0 ? window.length : end + 1;
if (nameBytes > STRUCTURE_BYTES)
throw new BinaryResourceFailure(
"structure",
STRUCTURE_BYTES,
"Decoded ELF section names exceed the 16 MiB structural budget",
);
decoded = {
name: end < 0 ? null : window.toString("latin1", 0, end),
state: end < 0 ? "truncated" : "known",
};
if (end < 0)
section_names_limitation ??=
"ELF section names are unavailable because a name is not NUL-terminated";
decodedNames.set(nameOffset, decoded);
}
name_state = decoded.state;
name = decoded.name;
}
}
const type = table.u32(h + 4);
const offset = table.word(h + (wide ? 24 : 16), bits);
const size = table.word(h + (wide ? 32 : 20), bits);
sections.push({
index,
name,
name_state,
header_offset: exact(sectionOffset) + h,
offset,
size,
address: table.word(h + (wide ? 16 : 12), bits),
type,
flags: table.word(h + 8, bits),
link: table.u32(h + (wide ? 40 : 24)),
info: table.u32(h + (wide ? 44 : 28)),
entry_size: table.word(h + (wide ? 56 : 36), bits),
file_backed:
type !== 8 && declaredFileRange(fileSize, offset, size).file_backed,
});
}
}
return {
format: "elf",
elf_os_abi: header.osAbi,
bits,
little,
machine,
flags: r.u32(wide ? 48 : 36),
...(section_names_limitation === undefined
? {}
: { section_names_limitation }),
programs: programDeclarations,
sections,
};
}
/** Shared COFF section fields; content extent excludes file-alignment padding. */
export const readPeSectionHeader = (bytes: Buffer) => {
const r = new BinaryReader(bytes, true);
const name = r.range(0, 8, "PE section name");
const nul = name.indexOf(0);
const virtual_size = r.u32(8);
const raw_size = r.u32(16);
return {
name: name.toString("latin1", 0, nul < 0 ? name.length : nul),
virtual_size,
virtual_address: r.u32(12),
raw_size,
offset: r.u32(20),
characteristics: r.u32(36),
size: virtual_size === 0 ? raw_size : Math.min(virtual_size, raw_size),
};
};
export function* readPeSectionDirectory(
fileSize: number,
): Generator<NativeReadRequest, NativeSectionDirectory, Buffer> {
const dos = new BinaryReader(yield request(fileSize, 0n, 64n), true);
if (dos.u16(0) !== 0x5a4d) reject("Missing DOS MZ header");
const pe = dos.u32(60);
if (pe < 64) reject("PE signature overlaps the DOS header");
const coff = new BinaryReader(yield request(fileSize, BigInt(pe), 24n), true);
if (coff.u32(0) !== 0x4550) reject("PE signature is invalid");
const optionalSize = coff.u16(20);
const optional = readPeOptionalHeaderStructure(
yield request(fileSize, BigInt(pe + 24), BigInt(optionalSize)),
);
if (!optional.ok) {
switch (optional.error.reason) {
case "truncated-magic":
return reject("16-bit field range is truncated or outside the file");
case "unsupported-magic":
throw new BinaryFormatFailure(
"unsupported",
"Only PE32/PE32+ images are supported",
);
case "truncated-fixed-fields":
case "truncated-directory-table":
return reject("PE optional header or data directories are truncated");
}
}
const { bits } = optional.value;
const count = coff.u16(6);
const start = pe + 24 + optionalSize;
const table = yield request(fileSize, BigInt(start), BigInt(count) * 40n);
const sections: NativeSection[] = [];
for (let index = 0; index < count; index++) {
const row = readPeSectionHeader(
table.subarray(index * 40, (index + 1) * 40),
);
sections.push({
index,
name: row.name,
name_state: "known",
header_offset: start + index * 40,
offset: BigInt(row.offset),
size: BigInt(row.size),
address: BigInt(row.virtual_address),
type: 0,
flags: BigInt(row.characteristics),
link: 0,
info: 0,
entry_size: 0n,
file_backed: declaredFileRange(
fileSize,
BigInt(row.offset),
BigInt(row.size),
).file_backed,
});
}
return {
format: "pe",
elf_os_abi: null,
bits,
little: true,
machine: coff.u16(4),
flags: coff.u16(22),
programs: [],
sections,
};
}
/** Borrow ELF bytes; file drivers satisfy the identical requests without loading payloads. */
export const readElfDirectoryFromBuffer = (
bytes: Buffer,
): NativeSectionDirectory => {
const steps = readElfSectionDirectory(bytes.length);
let next = steps.next();
while (!next.done) {
const { offset, size } = next.value;
next = steps.next(bytes.subarray(offset, offset + size));
}
return next.value;
};