fix(elf): follow loader version index precedence
VERDEF entries replace VERNEED names for shared indices, matching glibc lookup behavior.
This commit is contained in:
1 parent
446b284239
commit
642f6571ea
3 files changed
+77
-74
No files matched your search
@@ -334,11 +334,12 @@ count and SHA-256.
|
||||
`.gnu.version_r`, and the `hidden` flag from `.gnu.version`. `"unknown"`
|
||||
means the symbol-version table could not be read. A hidden version is not
|
||||
the default version that a by-name `dlsym` lookup selects.
|
||||
Definition names take precedence when both tables name the same index.
|
||||
|
||||
Unreadable names, out-of-range name ordinals and unreadable forwarders are
|
||||
listed in `issues`, their entries are not reported, and coverage is `partial`.
|
||||
Unresolved symbol versions keep their rows: the version name is null when no
|
||||
unambiguous definition or requirement names the index, and `version` is
|
||||
definition or requirement names the index, and `version` is
|
||||
`"unknown"` when the version table cannot be read; coverage is likewise
|
||||
`partial`. A dynamic symbol table without its null entry is malformed. An ELF
|
||||
without section headers is `unsupported`:
|
||||
|
||||
@@ -340,7 +340,7 @@ describe("ELF version requirements", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps conflicting names for one version index unknown", () => {
|
||||
it("uses a definition name when a requirement shares its index", () => {
|
||||
const result = parse(
|
||||
elfExportImage({
|
||||
symbols: [{ name: "ambiguous", version: 2 }],
|
||||
@@ -350,10 +350,13 @@ describe("ELF version requirements", () => {
|
||||
);
|
||||
|
||||
expect(result.exports).toMatchObject([
|
||||
{ name: "ambiguous", version: { index: 2, name: null, hidden: false } },
|
||||
{
|
||||
name: "ambiguous",
|
||||
version: { index: 2, name: "DEFINITION", hidden: false },
|
||||
},
|
||||
]);
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
expect(result.issues).toMatchObject([{ code: "version_unresolved" }]);
|
||||
expect(result.coverage.status).toBe("complete");
|
||||
expect(result.issues).toEqual([]);
|
||||
});
|
||||
|
||||
it("retains exports and reports a truncated version-requirement chain", () => {
|
||||
|
||||
@@ -290,76 +290,8 @@ const readVersionNames = (
|
||||
): Map<number, string | null> => {
|
||||
const namesByIndex = new Map<number, string | null>();
|
||||
const addName = (index: number, name: string): void => {
|
||||
const previous = namesByIndex.get(index);
|
||||
if (previous === undefined) namesByIndex.set(index, name || null);
|
||||
else if (previous !== name) namesByIndex.set(index, null);
|
||||
namesByIndex.set(index, name || null);
|
||||
};
|
||||
const verdef = sections.find(({ type }) => type === SHT_GNU_VERDEF);
|
||||
if (verdef !== undefined) {
|
||||
try {
|
||||
const { table, names } = versionSectionData(
|
||||
reader,
|
||||
sections,
|
||||
verdef,
|
||||
"Version definitions",
|
||||
);
|
||||
let offset = 0;
|
||||
for (let index = 0; index < verdef.info; index++) {
|
||||
if (offset + 20 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition chain leaves its section",
|
||||
);
|
||||
const at = table.offset + offset;
|
||||
const versionIndex = reader.u16(at + 4) & 0x7fff;
|
||||
const aux = reader.u32(at + 12);
|
||||
const next = reader.u32(at + 16);
|
||||
const auxiliaryCount = reader.u16(at + 6);
|
||||
if (auxiliaryCount === 0 || aux < 20 || offset + aux + 8 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition name entry leaves its section",
|
||||
);
|
||||
let auxiliary = offset + aux;
|
||||
const versionName = readElfString(
|
||||
names,
|
||||
reader.u32(table.offset + auxiliary),
|
||||
);
|
||||
addName(versionIndex, versionName);
|
||||
for (let item = 0; item < auxiliaryCount; item++) {
|
||||
if (auxiliary + 8 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition auxiliary chain leaves its section",
|
||||
);
|
||||
if (item + 1 < auxiliaryCount) {
|
||||
const auxiliaryNext = reader.u32(table.offset + auxiliary + 4);
|
||||
if (auxiliaryNext < 8)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition auxiliary chain ends before its declared count",
|
||||
);
|
||||
auxiliary += auxiliaryNext;
|
||||
}
|
||||
}
|
||||
if (index + 1 < verdef.info) {
|
||||
if (next < 20)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition chain ends before its declared count",
|
||||
);
|
||||
offset += next;
|
||||
}
|
||||
}
|
||||
} catch (cause) {
|
||||
issues.push({
|
||||
code: "version_unresolved",
|
||||
location: hex(verdef.offset),
|
||||
message: `Version definitions: ${failureMessage(cause)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const verneed = sections.find(({ type }) => type === SHT_GNU_VERNEED);
|
||||
if (verneed !== undefined) {
|
||||
try {
|
||||
@@ -430,6 +362,73 @@ const readVersionNames = (
|
||||
});
|
||||
}
|
||||
}
|
||||
// GNU lookup installs requirement names first, then definition names.
|
||||
const verdef = sections.find(({ type }) => type === SHT_GNU_VERDEF);
|
||||
if (verdef !== undefined) {
|
||||
try {
|
||||
const { table, names } = versionSectionData(
|
||||
reader,
|
||||
sections,
|
||||
verdef,
|
||||
"Version definitions",
|
||||
);
|
||||
let offset = 0;
|
||||
for (let index = 0; index < verdef.info; index++) {
|
||||
if (offset + 20 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition chain leaves its section",
|
||||
);
|
||||
const at = table.offset + offset;
|
||||
const versionIndex = reader.u16(at + 4) & 0x7fff;
|
||||
const aux = reader.u32(at + 12);
|
||||
const next = reader.u32(at + 16);
|
||||
const auxiliaryCount = reader.u16(at + 6);
|
||||
if (auxiliaryCount === 0 || aux < 20 || offset + aux + 8 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition name entry leaves its section",
|
||||
);
|
||||
let auxiliary = offset + aux;
|
||||
const versionName = readElfString(
|
||||
names,
|
||||
reader.u32(table.offset + auxiliary),
|
||||
);
|
||||
addName(versionIndex, versionName);
|
||||
for (let item = 0; item < auxiliaryCount; item++) {
|
||||
if (auxiliary + 8 > table.size)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition auxiliary chain leaves its section",
|
||||
);
|
||||
if (item + 1 < auxiliaryCount) {
|
||||
const auxiliaryNext = reader.u32(table.offset + auxiliary + 4);
|
||||
if (auxiliaryNext < 8)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition auxiliary chain ends before its declared count",
|
||||
);
|
||||
auxiliary += auxiliaryNext;
|
||||
}
|
||||
}
|
||||
if (index + 1 < verdef.info) {
|
||||
if (next < 20)
|
||||
throw new BinaryFormatFailure(
|
||||
"malformed",
|
||||
"Version definition chain ends before its declared count",
|
||||
);
|
||||
offset += next;
|
||||
}
|
||||
}
|
||||
} catch (cause) {
|
||||
issues.push({
|
||||
code: "version_unresolved",
|
||||
location: hex(verdef.offset),
|
||||
message: `Version definitions: ${failureMessage(cause)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return namesByIndex;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user