mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(native): reject nonzero reserved ELF dynamic symbols (#7528)
fix(native): validate the reserved ELF dynamic symbol Signed-off-by: Rudy Celekli <[email protected]>
This commit is contained in:
1 parent
21ad5b1f67
commit
7504f2ddfc
3 files changed
+106
-1
No files matched your search
@@ -821,6 +821,37 @@ describe("export inventory review regressions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([32, 64] as const)(
|
||||
"rejects nonzero reserved fields in the ELF%s null symbol",
|
||||
(bits) => {
|
||||
for (const little of [true, false]) {
|
||||
// ELF32/64 symbol records have no padding: every field in entry zero
|
||||
// is reserved as zero by the ABI, independently of host endianness.
|
||||
const width = bits === 64 ? 24 : 16;
|
||||
for (let byte = 0; byte < width; byte++) {
|
||||
const bytes = elfExportImage({
|
||||
bits,
|
||||
little,
|
||||
symbols: [{ name: "kept_export" }],
|
||||
});
|
||||
bytes[0x100 + byte] = 1;
|
||||
expect(() => parseNativeExports(bytes, artifact)).toThrow(
|
||||
BinaryFormatFailure,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
parse(
|
||||
elfExportImage({
|
||||
bits,
|
||||
little,
|
||||
symbols: [{ name: "kept_export" }],
|
||||
}),
|
||||
).exports.map(({ name }) => name),
|
||||
).toEqual(["kept_export"]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects a dynamic symbol table without its null entry", () => {
|
||||
expect(() =>
|
||||
parseNativeExports(
|
||||
|
||||
@@ -624,6 +624,13 @@ function* parseElfExports(
|
||||
width,
|
||||
"ELF dynamic symbols",
|
||||
);
|
||||
// Entry zero is the reserved STN_UNDEF record. Skipping it is valid only
|
||||
// after every field is observed as zero; neither ELF layout has padding.
|
||||
if (bytes.subarray(table, table + width).some((byte) => byte !== 0))
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"ELF dynamic symbol table null entry has nonzero fields",
|
||||
);
|
||||
const nameRange = reader.fileRange(
|
||||
strings.offset,
|
||||
strings.size,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import { nativeExportsSchema } from "../../../src/domain/native/nativeExports.js";
|
||||
import { elfExportImage } from "../../fixtures/binaryDiagnostics/nativeExports.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
@@ -67,3 +68,69 @@ cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
"rejects a non-null reserved ELF symbol through %s",
|
||||
async (surface, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-elf-null-symbol-");
|
||||
const mcp =
|
||||
surface === "stdio MCP" ? await connectLocalToolsMcp() : undefined;
|
||||
for (const encoding of [
|
||||
{ bits: 64, little: true },
|
||||
{ bits: 32, little: false },
|
||||
] as const) {
|
||||
const path = join(root, `exports-${encoding.bits}.so`);
|
||||
const bytes = elfExportImage({
|
||||
...encoding,
|
||||
symbols: [{ name: "kept_export" }],
|
||||
});
|
||||
// The authored fixture's dynamic symbol table starts at file offset 0x100.
|
||||
bytes[0x100] = 1;
|
||||
await writeFile(path, bytes);
|
||||
let error: unknown;
|
||||
if (surface === "CLI") {
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-native-exports", path, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(1);
|
||||
error = response.json;
|
||||
} else {
|
||||
const response = await mcp!.call("inspect_native_exports", { path });
|
||||
expect(response.isError).toBe(true);
|
||||
error = parseMcpToolError(response).error;
|
||||
}
|
||||
expect(error).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
issues: [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: expect.stringContaining("null entry"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
||||
bytes[0x100] = 0;
|
||||
await writeFile(path, bytes);
|
||||
const document =
|
||||
surface === "CLI"
|
||||
? (
|
||||
await cli.run({
|
||||
arguments: ["inspect-native-exports", path, "--json"],
|
||||
})
|
||||
).json
|
||||
: (await mcp!.call("inspect_native_exports", { path }))
|
||||
.structuredContent;
|
||||
const evidence = toolContract(
|
||||
"inspect_native_exports",
|
||||
).outputSchema.parse(document);
|
||||
expect(
|
||||
nativeExportsSchema
|
||||
.parse(evidence.normalized_result)
|
||||
.exports.map(({ name }) => name),
|
||||
).toEqual(["kept_export"]);
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user