mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 06:46:10 +00:00
refactor(elf): consolidate bounded string-table interpretation
This commit is contained in:
1 parent
e405d685bc
commit
624ebb2a1e
7 files changed
+75
-30
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/** Bounded source observation; consumers own scan limits, charging and caching. */
|
||||
export type ElfSectionNameObservation =
|
||||
export type ElfStringTableObservation =
|
||||
| {
|
||||
readonly state: "outside";
|
||||
readonly bytes: null;
|
||||
@@ -19,12 +19,12 @@ export type ElfSectionNameObservation =
|
||||
readonly contentBytes: number;
|
||||
};
|
||||
|
||||
/** Observe an ELF string-table name without treating a bounded missing NUL as absence. */
|
||||
export const observeElfSectionName = (
|
||||
/** Observe one ELF string-table entry without treating a bounded missing NUL as absence. */
|
||||
export const observeElfStringTableEntry = (
|
||||
table: Buffer,
|
||||
offset: number,
|
||||
maximumBytes: number,
|
||||
): ElfSectionNameObservation => {
|
||||
): ElfStringTableObservation => {
|
||||
if (offset < 0 || offset >= table.length)
|
||||
return { state: "outside", bytes: null, examinedBytes: 0, contentBytes: 0 };
|
||||
const window = table.subarray(offset, offset + maximumBytes);
|
||||
@@ -45,6 +45,6 @@ export const observeElfSectionName = (
|
||||
};
|
||||
|
||||
/** Project admitted source bytes without inferring an original text encoding. */
|
||||
export const elfSectionNameText = (
|
||||
observed: ElfSectionNameObservation,
|
||||
export const elfStringTableText = (
|
||||
observed: ElfStringTableObservation,
|
||||
): string | null => observed.bytes?.toString("latin1") ?? null;
|
||||
@@ -522,6 +522,32 @@ it("keeps section admission ahead of program admission for unrepresentable raw o
|
||||
});
|
||||
});
|
||||
|
||||
it.each([256, 257])(
|
||||
"preserves Dart's narrower symbol-name window for a %i-byte name",
|
||||
async (length) => {
|
||||
const name = "n".repeat(length);
|
||||
const original = buildElf64Image([{ name: "seed", value: 64, size: 4 }]);
|
||||
const sectionTable = Number(original.readBigUInt64LE(0x28));
|
||||
const stringHeader = sectionTable + 2 * 64;
|
||||
const stringOffset = Number(original.readBigUInt64LE(stringHeader + 0x18));
|
||||
const bytes = Buffer.concat([original, Buffer.alloc(length + 2)]);
|
||||
bytes.write(name, stringOffset + 1, "latin1");
|
||||
bytes[stringOffset + 1 + length] = 0;
|
||||
bytes.writeBigUInt64LE(BigInt(length + 2), stringHeader + 0x20);
|
||||
bytes.writeBigUInt64LE(BigInt(bytes.length), 64 + 0x20);
|
||||
expect(await readElfSymbols(bytes, [name])).toEqual(
|
||||
length === 256
|
||||
? { symbols: [{ name, offset: 64, size: 4 }], failures: [] }
|
||||
: {
|
||||
symbols: [],
|
||||
failures: [
|
||||
{ reason: `symbol ${name} is not in the dynamic symbol table` },
|
||||
],
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("checks raw symbol precision only after definition and requested-name selection", async () => {
|
||||
const bytes = buildElf64Image([
|
||||
{ name: "unselected", value: 64, size: 4 },
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
observeElfStringTableEntry,
|
||||
elfStringTableText,
|
||||
} from "../domain/elfStringTable.js";
|
||||
import { readElfDynamicEntry } from "../domain/elfDynamicEntry.js";
|
||||
import { readElfSectionHeader } from "../domain/elfSectionHeader.js";
|
||||
import { readElfProgramHeader } from "../domain/elfProgramHeader.js";
|
||||
@@ -35,14 +39,10 @@ class BoundedReader {
|
||||
}
|
||||
|
||||
cString(offset: number, tableEnd: number): string | null {
|
||||
if (offset < 0 || offset >= tableEnd || tableEnd > this.bytes.length)
|
||||
return null;
|
||||
const end = this.bytes
|
||||
.subarray(offset, Math.min(tableEnd, offset + 257))
|
||||
.indexOf(0);
|
||||
return end < 0
|
||||
? null
|
||||
: this.bytes.subarray(offset, offset + end).toString("latin1");
|
||||
if (tableEnd < 0 || tableEnd > this.bytes.length) return null;
|
||||
return elfStringTableText(
|
||||
observeElfStringTableEntry(this.bytes.subarray(0, tableEnd), offset, 257),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
observeElfSectionName,
|
||||
elfSectionNameText,
|
||||
} from "../../domain/elfSectionName.js";
|
||||
observeElfStringTableEntry,
|
||||
elfStringTableText,
|
||||
} from "../../domain/elfStringTable.js";
|
||||
import { readElfSectionHeader } from "../../domain/elfSectionHeader.js";
|
||||
import { readElfProgramHeader } from "../../domain/elfProgramHeader.js";
|
||||
import { readElfRawTableDeclarations } from "../../domain/elfRawTableDeclarations.js";
|
||||
@@ -128,7 +128,7 @@ class ElfReader {
|
||||
offset: number,
|
||||
text: TextBudget,
|
||||
): { name: string | null; limitation: string | null } {
|
||||
const observed = observeElfSectionName(
|
||||
const observed = observeElfStringTableEntry(
|
||||
table,
|
||||
offset,
|
||||
text.limit - text.used + 1,
|
||||
@@ -141,7 +141,7 @@ class ElfReader {
|
||||
};
|
||||
chargeText(text, observed.contentBytes);
|
||||
return {
|
||||
name: elfSectionNameText(observed),
|
||||
name: elfStringTableText(observed),
|
||||
limitation:
|
||||
observed.state === "truncated"
|
||||
? "ELF section names are incomplete because a name is not NUL-terminated."
|
||||
|
||||
@@ -677,6 +677,26 @@ describe("export inventory review regressions", () => {
|
||||
});
|
||||
|
||||
describe("ELF symbol-name text", () => {
|
||||
it.each([65535, 65536])(
|
||||
"keeps the ELF export name scan bounded when its NUL follows %i bytes",
|
||||
(length) => {
|
||||
const name = "n".repeat(length);
|
||||
const result = parse(elfExportImage({ symbols: [{ name }] }));
|
||||
if (result.format !== "elf") throw new TypeError("expected ELF result");
|
||||
expect(result.exports.map((entry) => entry.name)).toEqual(
|
||||
length === 65535 ? [name] : [],
|
||||
);
|
||||
if (length === 65536)
|
||||
expect(result.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining(
|
||||
"unterminated or exceeds the string budget",
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("decodes UTF-8 names and retains exact bytes", () => {
|
||||
const result = parse(elfExportImage({ symbols: [{ name: "café" }] }));
|
||||
expect(result.format).toBe("elf");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { observeElfStringTableEntry } from "../../domain/elfStringTable.js";
|
||||
import { BinaryFormatFailure } from "./BinaryContainer.js";
|
||||
|
||||
export const readElfString = (bytes: Buffer, offset: number): string => {
|
||||
@@ -6,18 +7,16 @@ export const readElfString = (bytes: Buffer, offset: number): string => {
|
||||
|
||||
/** Read exact string-table bytes; ELF does not prescribe a character encoding. */
|
||||
export const readElfStringBytes = (bytes: Buffer, offset: number): Buffer => {
|
||||
if (offset >= bytes.length)
|
||||
const observed = observeElfStringTableEntry(bytes, offset, 65536);
|
||||
if (observed.state === "outside")
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"ELF string offset is outside its string table",
|
||||
);
|
||||
const end = bytes
|
||||
.subarray(offset, Math.min(bytes.length, offset + 65536))
|
||||
.indexOf(0);
|
||||
if (end < 0)
|
||||
if (observed.state === "truncated")
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"ELF string is unterminated or exceeds the string budget",
|
||||
);
|
||||
return bytes.subarray(offset, offset + end);
|
||||
return observed.bytes;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
observeElfSectionName,
|
||||
elfSectionNameText,
|
||||
} from "../../domain/elfSectionName.js";
|
||||
observeElfStringTableEntry,
|
||||
elfStringTableText,
|
||||
} from "../../domain/elfStringTable.js";
|
||||
import { observeMzWindowsHeader } from "../../domain/dosMz.js";
|
||||
import { readElfSectionHeader } from "../../domain/elfSectionHeader.js";
|
||||
import { readElfProgramHeader } from "../../domain/elfProgramHeader.js";
|
||||
@@ -221,7 +221,7 @@ export function* readElfSectionDirectory(
|
||||
if (names !== null) {
|
||||
let decoded = decodedNames.get(nameOffset);
|
||||
if (decoded === undefined) {
|
||||
const observed = observeElfSectionName(names, nameOffset, 65536);
|
||||
const observed = observeElfStringTableEntry(names, nameOffset, 65536);
|
||||
nameBytes += observed.examinedBytes;
|
||||
if (nameBytes > STRUCTURE_BYTES)
|
||||
throw new BinaryResourceFailure(
|
||||
@@ -230,7 +230,7 @@ export function* readElfSectionDirectory(
|
||||
"Decoded ELF section names exceed the 16 MiB structural budget",
|
||||
);
|
||||
decoded = {
|
||||
name: elfSectionNameText(observed),
|
||||
name: elfStringTableText(observed),
|
||||
state:
|
||||
observed.state === "terminated"
|
||||
? "known"
|
||||
|
||||
Reference in new issue
Block a user