mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(flutter): check section-backed symbol tables against PT_LOAD (#3859)
* fix(flutter): check section-backed symbol tables against PT_LOAD The section-backed ELF path read .dynsym and .dynstr only through their section file offsets. The stripped PT_DYNAMIC path and snapshot symbols resolve through the PT_LOAD map. A second PT_LOAD interpretation over an allocated symbol or string table was therefore ignored, and inspect-dart-aot reported complete coverage from metadata the loader may read differently. An allocated (SHF_ALLOC) dynamic symbol table and its string table now need a single file-backed PT_LOAD interpretation that matches the section offset, the same check the dynamic path applies to its tables. Otherwise the symbol inventory is refused with the mapping reason, and inspect-dart-aot reports that no recognizable snapshot selection remains. Non-allocated tables keep their section-offset reading. Fixes #3346 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(flutter): cover allocated ELF32 symbol-table mapping checks Add an ELF32 (ARM) counterpart of the synthetic ELF64 image and run the allocated-table cases for both classes, so the ELF32 section-header reads in the mapping check are exercised: matching mappings, conflicting symbol and string mappings, and a disagreeing section offset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
933f216698
commit
1b036ecbff
3 files changed
+191
No files matched your search
@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { readElfSymbols, readSnapshotHeader } from "./DartElf.js";
|
||||
import {
|
||||
REFERENCE_HASH,
|
||||
buildElf32Image,
|
||||
buildElf64Image,
|
||||
buildStrippedElf64Image,
|
||||
snapshotSection,
|
||||
@@ -299,6 +300,106 @@ describe("ELF symbol-table ownership", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** Section-header field positions for each ELF class. */
|
||||
const ELF_CLASSES = {
|
||||
elf64: {
|
||||
build: buildElf64Image,
|
||||
word: {
|
||||
read: (image: Buffer, at: number) => Number(image.readBigUInt64LE(at)),
|
||||
write: (image: Buffer, value: number, at: number) =>
|
||||
image.writeBigUInt64LE(BigInt(value), at),
|
||||
},
|
||||
shoff: 0x28,
|
||||
shent: 64,
|
||||
flags: 8,
|
||||
address: 0x10,
|
||||
offset: 0x18,
|
||||
},
|
||||
elf32: {
|
||||
build: buildElf32Image,
|
||||
word: {
|
||||
read: (image: Buffer, at: number) => image.readUInt32LE(at),
|
||||
write: (image: Buffer, value: number, at: number) =>
|
||||
image.writeUInt32LE(value, at),
|
||||
},
|
||||
shoff: 0x20,
|
||||
shent: 40,
|
||||
flags: 8,
|
||||
address: 0x0c,
|
||||
offset: 0x10,
|
||||
},
|
||||
} as const;
|
||||
|
||||
describe.each(Object.entries(ELF_CLASSES))(
|
||||
"allocated %s symbol tables",
|
||||
(_, layout) => {
|
||||
const { word } = layout;
|
||||
/** Snapshot sections, with dynamic tables allocated at their offsets. */
|
||||
const allocatedTables = (): {
|
||||
image: Buffer;
|
||||
headers: number[];
|
||||
tables: number[];
|
||||
} => {
|
||||
const elf = layout.build([
|
||||
{ name: "_kDartVmSnapshotData", value: 0x400, size: 52 },
|
||||
]);
|
||||
const section = snapshotSection(GALLERY_HASH);
|
||||
const image = Buffer.concat([
|
||||
elf,
|
||||
Buffer.alloc(0x400 - elf.length),
|
||||
section,
|
||||
]);
|
||||
const shoff = word.read(image, layout.shoff);
|
||||
const headers = [shoff + layout.shent, shoff + 2 * layout.shent];
|
||||
const tables = headers.map((header) => {
|
||||
const offset = word.read(image, header + layout.offset);
|
||||
word.write(image, 2, header + layout.flags);
|
||||
word.write(image, offset, header + layout.address);
|
||||
return offset;
|
||||
});
|
||||
return { image, headers, tables };
|
||||
};
|
||||
|
||||
it("accepts tables whose mapping matches their sections", async () => {
|
||||
const { image } = allocatedTables();
|
||||
expect(await readElfSymbols(image, ["_kDartVmSnapshotData"])).toEqual({
|
||||
symbols: [{ name: "_kDartVmSnapshotData", offset: 0x400, size: 52 }],
|
||||
failures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[0, "Dynamic symbol table has conflicting PT_LOAD mappings"],
|
||||
[1, "Dynamic symbol string table has conflicting PT_LOAD mappings"],
|
||||
])(
|
||||
"refuses table %i under a conflicting PT_LOAD interpretation",
|
||||
async (table, reason) => {
|
||||
const { image, tables } = allocatedTables();
|
||||
const conflict = appendElfRanges(image, "program", [
|
||||
{ type: 1, address: tables[table]!, offset: 0x300, size: 4 },
|
||||
]);
|
||||
expect(
|
||||
await readElfSymbols(conflict, ["_kDartVmSnapshotData"]),
|
||||
).toEqual({ symbols: [], failures: [{ reason }] });
|
||||
},
|
||||
);
|
||||
|
||||
it("refuses a table whose section offset disagrees with its mapping", async () => {
|
||||
const { image, headers } = allocatedTables();
|
||||
word.write(image, 0x300, headers[0]! + layout.address);
|
||||
expect(await readElfSymbols(image, ["_kDartVmSnapshotData"])).toEqual({
|
||||
symbols: [],
|
||||
failures: [
|
||||
{
|
||||
reason:
|
||||
"Dynamic symbol table section offset disagrees with its PT_LOAD mapping",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("readSnapshotHeader", () => {
|
||||
it("reads a synthesized data header", async () => {
|
||||
const header = readSnapshotHeader(snapshotSection(GALLERY_HASH), 0);
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface ElfParseFailure {
|
||||
}
|
||||
|
||||
const SHT_DYNSYM = 11;
|
||||
const SHF_ALLOC = 2;
|
||||
const PT_LOAD = 1;
|
||||
const PT_DYNAMIC = 2;
|
||||
/** Position-independent executable images carry PT_DYNAMIC. */
|
||||
@@ -225,6 +226,28 @@ export const readElfSymbols = async (
|
||||
stringOffset: number;
|
||||
stringSize: number;
|
||||
}[] = [];
|
||||
/**
|
||||
* The loader reads an allocated section through PT_LOAD, so its section
|
||||
* offset must be the single file interpretation of its address range.
|
||||
*/
|
||||
const sectionMappingFailure = (
|
||||
header: number,
|
||||
offset: number,
|
||||
size: number,
|
||||
): string | undefined => {
|
||||
const flags = is64 ? reader.u64(header + 8) : reader.u32(header + 8);
|
||||
const address = is64
|
||||
? reader.u64(header + 0x10)
|
||||
: reader.u32(header + 0x0c);
|
||||
if (flags === null || address === null)
|
||||
return "has an unrepresentable section header";
|
||||
if ((flags & SHF_ALLOC) === 0) return undefined;
|
||||
const mapped = toOffset(address, size);
|
||||
if (!mapped.ok) return mapped.error.reason;
|
||||
return mapped.value === offset
|
||||
? undefined
|
||||
: "section offset disagrees with its PT_LOAD mapping";
|
||||
};
|
||||
if (!dynamicFallback) {
|
||||
for (let index = 0; index < e_shnum; index += 1) {
|
||||
if (index % 4096 === 0) {
|
||||
@@ -269,6 +292,16 @@ export const readElfSymbols = async (
|
||||
return failure(
|
||||
"Dynamic symbol strings are out of bounds or not a string table",
|
||||
);
|
||||
const symbolMapping = sectionMappingFailure(header, offset, size);
|
||||
if (symbolMapping !== undefined)
|
||||
return failure(`Dynamic symbol table ${symbolMapping}`);
|
||||
const stringMapping = sectionMappingFailure(
|
||||
stringHeader,
|
||||
stringOffset,
|
||||
stringSize,
|
||||
);
|
||||
if (stringMapping !== undefined)
|
||||
return failure(`Dynamic symbol string table ${stringMapping}`);
|
||||
tables.push({
|
||||
offset,
|
||||
count: size / entrySize,
|
||||
|
||||
@@ -115,6 +115,63 @@ export const buildElf64Image = (
|
||||
return image;
|
||||
};
|
||||
|
||||
/** The ELF32 (ARM) counterpart of {@link buildElf64Image}, with the same layout roles. */
|
||||
export const buildElf32Image = (
|
||||
symbols: ReadonlyArray<{ name: string; value: number; size: number }>,
|
||||
): Buffer => {
|
||||
const ELF_HEADER = 52;
|
||||
const PHENT = 32;
|
||||
const SHENT = 40;
|
||||
const SYMENT = 16;
|
||||
const phoff = ELF_HEADER;
|
||||
const shoff = phoff + PHENT;
|
||||
const dynsymOff = shoff + 3 * SHENT + 16;
|
||||
const dynstrOff = dynsymOff + symbols.length * SYMENT;
|
||||
const image = Buffer.alloc(dynstrOff + 256);
|
||||
image.writeUInt32LE(0x464c457f, 0);
|
||||
image[4] = 1; // ELFCLASS32
|
||||
image[5] = 1; // little-endian
|
||||
image[6] = 1;
|
||||
image.writeUInt16LE(3, 0x10);
|
||||
image.writeUInt16LE(0x0028, 0x12); // e_machine ARM
|
||||
image.writeUInt32LE(1, 0x14);
|
||||
image.writeUInt32LE(phoff, 0x1c);
|
||||
image.writeUInt32LE(shoff, 0x20);
|
||||
image.writeUInt16LE(ELF_HEADER, 0x28);
|
||||
image.writeUInt16LE(PHENT, 0x2a);
|
||||
image.writeUInt16LE(1, 0x2c);
|
||||
image.writeUInt16LE(SHENT, 0x2e);
|
||||
image.writeUInt16LE(3, 0x30); // null, dynsym, dynstr
|
||||
// One identity PT_LOAD covering the materialized payload.
|
||||
image.writeUInt32LE(1, phoff);
|
||||
image.writeUInt32LE(
|
||||
Math.max(image.length, ...symbols.map(({ value, size }) => value + size)),
|
||||
phoff + 0x10,
|
||||
);
|
||||
const dyn = shoff + SHENT;
|
||||
image.writeUInt32LE(11, dyn + 4); // SHT_DYNSYM
|
||||
image.writeUInt32LE(dynsymOff, dyn + 0x10);
|
||||
image.writeUInt32LE(symbols.length * SYMENT, dyn + 0x14);
|
||||
image.writeUInt32LE(2, dyn + 0x18); // sh_link -> strtab
|
||||
image.writeUInt32LE(SYMENT, dyn + 0x24);
|
||||
const str = dyn + SHENT;
|
||||
image.writeUInt32LE(3, str + 4); // SHT_STRTAB
|
||||
image.writeUInt32LE(dynstrOff, str + 0x10);
|
||||
image.writeUInt32LE(256, str + 0x14);
|
||||
let strCursor = dynstrOff + 1;
|
||||
symbols.forEach((symbol, index) => {
|
||||
image.write(symbol.name, strCursor, "latin1");
|
||||
const base = dynsymOff + index * SYMENT;
|
||||
image.writeUInt32LE(strCursor - dynstrOff, base);
|
||||
image.writeUInt32LE(symbol.value, base + 4);
|
||||
image.writeUInt32LE(symbol.size, base + 8);
|
||||
image[base + 0xc] = 0x10; // GLOBAL
|
||||
image.writeUInt16LE(1, base + 0xe); // Defined export.
|
||||
strCursor += symbol.name.length + 1;
|
||||
});
|
||||
return image;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a section-stripped ELF64 image (e_shnum 0, ET_DYN) whose exported
|
||||
* symbols are reachable only through the PT_DYNAMIC tag-value pairs, the
|
||||
|
||||
Reference in new issue
Block a user