mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix: skip unrelated kernel scans during ELF layout inspection
This commit is contained in:
1 parent
06fe7eecbc
commit
dba4178465
3 files changed
+92
-15
No files matched your search
@@ -132,6 +132,11 @@ def inspect_elf(path, cache):
|
||||
try:
|
||||
headers = ELFFile(io.BytesIO(content))
|
||||
section_count = headers.num_sections()
|
||||
if section_count:
|
||||
location(headers.header.e_shoff, section_count * headers.header.e_shentsize, len(content))
|
||||
segment_count = headers.num_segments()
|
||||
if segment_count:
|
||||
location(headers.header.e_phoff, segment_count * headers.header.e_phentsize, len(content))
|
||||
if headers.header.e_shstrndx == 0xffff and section_count == 0:
|
||||
raise LayoutFailure("format", "ELF extended section-name index requires a declared section-zero header.")
|
||||
raw_name_index = headers.header.e_shstrndx
|
||||
@@ -147,9 +152,21 @@ def inspect_elf(path, cache):
|
||||
if names_header.sh_type != "SHT_STRTAB":
|
||||
raise LayoutFailure("format", f"ELF section-name table {section_name_index} is not SHT_STRTAB.")
|
||||
location(names_header.sh_offset, names_header.sh_size, len(content))
|
||||
except ELFError as error:
|
||||
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
|
||||
raise LayoutFailure("format", str(error)) from error
|
||||
class LayoutELF(ELF):
|
||||
def search(self, needle, writable=False, executable=False):
|
||||
# This adapter exposes structural facts, GOT/PLT and mitigation
|
||||
# indicators, not kernel config/version discovery. Upstream's
|
||||
# constructor searches virtual memory for those unrelated
|
||||
# heuristics, materializing NOBITS tails and interpreting
|
||||
# ordinary IKCFG_ST data as a compressed kernel config.
|
||||
return iter(())
|
||||
|
||||
def _populate_kernel_version(self):
|
||||
# Kernel banner decoding is outside this report too.
|
||||
return None
|
||||
|
||||
def _get_section_name(self, header):
|
||||
# SHN_UNDEF declares no names; upstream would otherwise read
|
||||
# strings at the inactive section-zero offset.
|
||||
@@ -294,6 +311,7 @@ def inspect_elf(path, cache):
|
||||
"Relocatable relocation targets with SHN_UNDEF or inactive SHT_NULL headers retain their reported section index/offset as unknown; they do not establish an associated target section.",
|
||||
"GOT/PLT are derived upstream convenience maps; PLT inference can emulate selected instructions in Unicorn. They do not establish target runtime behavior; aliases may collapse and warnings may indicate incomplete coverage. Their completeness is unknown.",
|
||||
"Mitigations are upstream static heuristics, not runtime protection. ET_DYN does not prove an executable; absent canary symbols do not prove every function unprotected.",
|
||||
"Kernel configuration/version discovery is not performed; these memory-content scans do not supply the reported structural, linkage or mitigation facts.",
|
||||
"This profile inspects ELF EXEC/DYN/REL layout only. It does not analyze recorded cores, resolve loaded libraries, launch the target as a host process or start a debugger.",
|
||||
"Symbol/relocation inventories reflect original section tables. A sectionless image can still report dynamic dependency names; missing tables do not prove absence of dynamic symbols or relocations.",
|
||||
*(["Absent section-name table: section-dependent RELRO/canary heuristics may be incomplete; protection coverage is unknown."] if all_sections and section_name_index == 0 else []),
|
||||
@@ -302,7 +320,7 @@ def inspect_elf(path, cache):
|
||||
}
|
||||
except LayoutFailure:
|
||||
raise
|
||||
except (ELFError, ValueError, AssertionError, IndexError) as error:
|
||||
except (ELFError, ValueError, OverflowError, AssertionError, IndexError) as error:
|
||||
raise LayoutFailure("format", "Selected ELF failed unchanged upstream structural parsing: " + str(error)) from error
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,16 @@ the selected object as a host process, or requests runtime library resolution. T
|
||||
uses isolated mode and an owned cache. Exact upstream profiles are recorded in
|
||||
[upstream provenance](https://github.com/morluto/rea/blob/main/third_party/pwntools/README.md).
|
||||
|
||||
The adapter skips pwntools kernel configuration and version discovery: those
|
||||
memory-content scans supply none of this report's fields. Ordinary `IKCFG_ST`
|
||||
data is not interpreted as a kernel-config request, and virtual zero-filled
|
||||
LOAD tails are reported from their headers without materializing them for that
|
||||
scan. GOT/PLT inference and the advertised static mitigation indicators still
|
||||
use the pinned upstream implementations. Header-table file ranges are checked
|
||||
before dependency seeks. Structural inventory and the convenience-map engine
|
||||
still parse the snapshot separately; eliminating that remaining duplication
|
||||
requires preserving the upstream GOT/PLT and mitigation facts.
|
||||
|
||||
## Interpreting results
|
||||
|
||||
- Artifact path, SHA-256 and size identify original selected bytes. All parsing
|
||||
|
||||
@@ -329,6 +329,48 @@ try {
|
||||
}
|
||||
}
|
||||
const protectedReport = reports.get("protected");
|
||||
// Ordinary data markers are not a request to discover/decompress a kernel config.
|
||||
const markerBytes = Buffer.from(await readFile(join(root.path, "protected")));
|
||||
const markerOffset = markerBytes.indexOf(Buffer.from("source-owned"));
|
||||
assert.ok(markerOffset >= 0);
|
||||
markerBytes.write("IKCFG_ST", markerOffset, "ascii");
|
||||
const markerPath = join(root.path, "ordinary-kernel-marker");
|
||||
await writeFile(markerPath, markerBytes);
|
||||
for (const mode of ["cli", "mcp"]) {
|
||||
const value = await inspect(mode, markerPath);
|
||||
for (const field of [
|
||||
"sections",
|
||||
"segments",
|
||||
"symbols",
|
||||
"relocations",
|
||||
"linkage",
|
||||
"mitigations",
|
||||
])
|
||||
assert.deepEqual(value[field], protectedReport[field]);
|
||||
assert.equal(
|
||||
value.artifact.sha256,
|
||||
createHash("sha256").update(markerBytes).digest("hex"),
|
||||
);
|
||||
cases++;
|
||||
}
|
||||
assert.deepEqual(await readFile(markerPath), markerBytes);
|
||||
// File-table offsets are validated before narrower dependency seek APIs.
|
||||
const overflowingTable = Buffer.from(markerBytes);
|
||||
overflowingTable.writeBigUInt64LE(0x8000000000000040n, 32);
|
||||
const overflowingTablePath = join(root.path, "overflowing-program-table");
|
||||
await writeFile(overflowingTablePath, overflowingTable);
|
||||
for (const mode of ["cli", "mcp"]) {
|
||||
const error = await inspect(mode, overflowingTablePath, "invalid_input");
|
||||
assert.equal(error.code, "invalid_request");
|
||||
assert.ok(
|
||||
error.details.issues.some(
|
||||
({ path, reason }) => path[0] === "path" && reason === "invalid_format",
|
||||
),
|
||||
);
|
||||
cases++;
|
||||
}
|
||||
assert.deepEqual(await readFile(overflowingTablePath), overflowingTable);
|
||||
|
||||
const fileSymbols = protectedReport.symbols.filter(
|
||||
(symbol) => symbol.type === "STT_FILE",
|
||||
);
|
||||
@@ -927,9 +969,8 @@ try {
|
||||
sectionHeavy.writeUInt16LE(sectionCount, 60);
|
||||
const sectionHeavyPath = join(root.path, "section-heavy.o");
|
||||
await writeFile(sectionHeavyPath, sectionHeavy);
|
||||
// pwntools materializes each PT_LOAD zero-filled tail during inspection.
|
||||
// One allocation larger than the whole address-space budget fails promptly;
|
||||
// a large section count instead races CPU work against the command deadline.
|
||||
// A structural report must retain a large virtual zero tail without allocating it.
|
||||
// Bootstrap controls above retain genuine MemoryError/ENOMEM classification.
|
||||
const memoryHeavy = Buffer.from(await readFile(join(root.path, "protected")));
|
||||
const memoryPhOffset = Number(memoryHeavy.readBigUInt64LE(32));
|
||||
const memoryPhSize = memoryHeavy.readUInt16LE(54);
|
||||
@@ -944,7 +985,9 @@ try {
|
||||
assert.notEqual(lastLoadIndex, undefined);
|
||||
const memorySizeOffset = memoryPhOffset + lastLoadIndex * memoryPhSize + 40;
|
||||
memoryHeavy.writeBigUInt64LE(
|
||||
memoryHeavy.readBigUInt64LE(memorySizeOffset) + 128n * 1024n * 1024n,
|
||||
memoryHeavy.readBigUInt64LE(memorySizeOffset) +
|
||||
3n * 1024n * 1024n * 1024n +
|
||||
1n,
|
||||
memorySizeOffset,
|
||||
);
|
||||
const memoryHeavyPath = join(root.path, "memory-zero-tail");
|
||||
@@ -963,21 +1006,27 @@ try {
|
||||
try {
|
||||
await memoryClient.connect(memoryTransport);
|
||||
for (const mode of ["cli", "mcp"]) {
|
||||
const error = await inspect(
|
||||
const value = await inspect(
|
||||
mode,
|
||||
memoryHeavyPath,
|
||||
"resource_constraint",
|
||||
undefined,
|
||||
memoryEnvironment,
|
||||
memoryClient,
|
||||
);
|
||||
assert.equal(error.code, "resource_constraint");
|
||||
assert.equal(error.details.resource, "memory");
|
||||
assert.ok(
|
||||
error.details.reported_limits === null ||
|
||||
error.details.reported_limits.address_space_bytes === 100663296,
|
||||
const original = protectedReport.segments[lastLoadIndex];
|
||||
assert.deepEqual(value.segments[lastLoadIndex], {
|
||||
...original,
|
||||
memory_size: `0x${memoryHeavy.readBigUInt64LE(memorySizeOffset).toString(16)}`,
|
||||
});
|
||||
assert.deepEqual(value.sections, protectedReport.sections);
|
||||
assert.deepEqual(value.symbols, protectedReport.symbols);
|
||||
assert.deepEqual(value.relocations, protectedReport.relocations);
|
||||
assert.deepEqual(value.linkage, protectedReport.linkage);
|
||||
assert.deepEqual(value.mitigations, protectedReport.mitigations);
|
||||
assert.equal(
|
||||
value.artifact.sha256,
|
||||
createHash("sha256").update(memoryHeavy).digest("hex"),
|
||||
);
|
||||
assert.equal(error.details.captured_output.truncated, false);
|
||||
assert.ok(error.remediation.action.includes("memory"));
|
||||
cases++;
|
||||
}
|
||||
assert.deepEqual(await readFile(memoryHeavyPath), memoryHeavy);
|
||||
|
||||
Reference in new issue
Block a user