mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 11:56:12 +00:00
fix(exports): preserve ELF name byte identity
This commit is contained in:
1 parent
1cd82597da
commit
14dd07e740
4 files changed
+199
-24
No files matched your search
@@ -7,7 +7,6 @@ import {
|
||||
readMappedCString,
|
||||
} from "../metadata/BinaryContainer.js";
|
||||
import { architectureForElfMachine } from "../metadata/ElfImage.js";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
import { readElfStringBytes } from "../metadata/ElfStringTable.js";
|
||||
import { readElfSymbol } from "../../domain/elfSymbol.js";
|
||||
import {
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
import { readPeImage } from "../metadata/PeImage.js";
|
||||
import {
|
||||
MAX_NATIVE_EXPORT_ROWS,
|
||||
decodeElfExportName,
|
||||
type ElfExport,
|
||||
type NativeExportIssue,
|
||||
type NativeExports,
|
||||
@@ -304,22 +304,7 @@ const versionSectionData = (
|
||||
};
|
||||
|
||||
/** Version names by index, read from `.gnu.version_d` and `.gnu.version_r`. */
|
||||
type DecodedElfString = {
|
||||
readonly name: string;
|
||||
readonly name_bytes_base64: string | null;
|
||||
readonly name_utf8_decodable: boolean;
|
||||
};
|
||||
|
||||
const decodeElfString = (bytes: Buffer): DecodedElfString => {
|
||||
const utf8 = isUtf8(bytes);
|
||||
return {
|
||||
name: bytes.toString(utf8 ? "utf8" : "latin1"),
|
||||
name_bytes_base64: bytes.some((byte) => byte >= 0x80)
|
||||
? bytes.toString("base64")
|
||||
: null,
|
||||
name_utf8_decodable: utf8,
|
||||
};
|
||||
};
|
||||
type DecodedElfString = ReturnType<typeof decodeElfExportName>;
|
||||
|
||||
const readVersionNames = (
|
||||
reader: BinaryReader,
|
||||
@@ -331,7 +316,7 @@ const readVersionNames = (
|
||||
// output rows. Replacing an index releases its previous decoded name.
|
||||
let textBytes = 0;
|
||||
const addName = (index: number, name: Buffer): void => {
|
||||
const decoded = name.length === 0 ? null : decodeElfString(name);
|
||||
const decoded = name.length === 0 ? null : decodeElfExportName(name);
|
||||
const previous = namesByIndex.get(index);
|
||||
const nextTextBytes =
|
||||
textBytes -
|
||||
@@ -641,7 +626,7 @@ const parseElfExports = (bytes: Buffer, artifact: Artifact): NativeExports => {
|
||||
let rawNameBase64: string | null;
|
||||
try {
|
||||
const rawName = readElfStringBytes(names, symbol.name_offset);
|
||||
const decodedName = decodeElfString(rawName);
|
||||
const decodedName = decodeElfExportName(rawName);
|
||||
nameUtf8Decodable = decodedName.name_utf8_decodable;
|
||||
name = decodedName.name;
|
||||
rawNameBase64 = decodedName.name_bytes_base64;
|
||||
|
||||
@@ -118,7 +118,7 @@ it("qualifies projected names with exact encoding facts and normalizes historica
|
||||
analysisViewRequestSchema.parse({
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["café"],
|
||||
indices: [3],
|
||||
fields: ["name"],
|
||||
}),
|
||||
);
|
||||
@@ -131,6 +131,66 @@ it("qualifies projected names with exact encoding facts and normalizes historica
|
||||
}
|
||||
});
|
||||
|
||||
it("matches ELF UTF-8 identities without conflating Latin-1 display names or unknown encodings", () => {
|
||||
const report = parseNativeExports(
|
||||
elfExportImage({
|
||||
symbols: [{ name: "café" }, { name: "café" }, { name: "same" }],
|
||||
}),
|
||||
artifact,
|
||||
);
|
||||
expect(
|
||||
project(report, {
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["café"],
|
||||
fields: ["name"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
items: [{ source_index: 0, name: "café", name_utf8_decodable: true }],
|
||||
coverage: { total: 1 },
|
||||
});
|
||||
if (report.format !== "elf") throw new Error("Expected ELF fixture");
|
||||
const historical = {
|
||||
...report,
|
||||
exports: report.exports.map(
|
||||
({ name_bytes_base64: _bytes, name_utf8_decodable: _utf8, ...row }) =>
|
||||
row,
|
||||
),
|
||||
};
|
||||
expect(
|
||||
project(historical, {
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["café"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
items: [],
|
||||
unknowns: [expect.stringContaining("lack retained encoding facts")],
|
||||
});
|
||||
expect(
|
||||
project(historical, {
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: [],
|
||||
}),
|
||||
).toMatchObject({ items: [], unknowns: [] });
|
||||
const missingBytes = {
|
||||
...report,
|
||||
exports: report.exports.map((row) => ({ ...row, name_bytes_base64: null })),
|
||||
};
|
||||
expect(
|
||||
project(missingBytes, {
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["café", "same"],
|
||||
fields: [],
|
||||
}),
|
||||
).toMatchObject({
|
||||
items: [{ source_index: 2 }],
|
||||
unknowns: [expect.stringContaining("lack retained encoding facts")],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fill historical name defaults into a projection that omits names", () => {
|
||||
const result = project(elf(), {
|
||||
kind: "native-exports",
|
||||
@@ -146,6 +206,54 @@ it("does not fill historical name defaults into a projection that omits names",
|
||||
expect(view_digest).toBe(digestCanonicalValue(content, "Analysis view"));
|
||||
});
|
||||
|
||||
it.each(["Y2Fmw6k", "Y2Fmw6l=", "not base64"])(
|
||||
"rejects untrustworthy imported ELF name bytes %s before selection",
|
||||
(encoded) => {
|
||||
for (const target of ["symbol", "version"]) {
|
||||
const report = elf();
|
||||
const row = report.exports[0]!;
|
||||
if (target === "symbol") row.name_bytes_base64 = encoded;
|
||||
else if (typeof row.version === "object" && row.version !== null)
|
||||
row.version.name_bytes_base64 = encoded;
|
||||
const result = projectAnalysisView(
|
||||
parent(report),
|
||||
analysisViewRequestSchema.parse({
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["missing"],
|
||||
}),
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["text", "encoding"])(
|
||||
"rejects contradictory ELF %s facts at the source owner",
|
||||
(fact) => {
|
||||
for (const target of ["symbol", "version"]) {
|
||||
const report = elf();
|
||||
const row = report.exports[0]!;
|
||||
const name = target === "symbol" ? row : row.version;
|
||||
if (name === null || typeof name !== "object")
|
||||
throw new Error("Expected named ELF row");
|
||||
name.name_bytes_base64 = Buffer.from(name.name!).toString("base64");
|
||||
if (fact === "text") name.name = "different";
|
||||
else name.name_utf8_decodable = false;
|
||||
expect(
|
||||
projectAnalysisView(
|
||||
parent(report),
|
||||
analysisViewRequestSchema.parse({
|
||||
kind: "native-exports",
|
||||
collection: "exports",
|
||||
names: ["missing"],
|
||||
}),
|
||||
).ok,
|
||||
).toBe(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves PE ordinal-only, forwarder, alias and same-address distinctions", () => {
|
||||
const report = parseNativeExports(
|
||||
peExportImage({
|
||||
|
||||
Reference in new issue
Block a user