fix(desktop): preserve literal directories in ELF origin expansion (#10215)

fix(desktop): expand ELF origin paths as literal directory names
This commit is contained in:
Kaoru0822 authored and GitHub committed 2026-11-12 19:33:16 -05:00
1 parent 966bde3780
commit c19d319929
3 files changed
+77 -4

No files matched your search

@@ -10,6 +10,50 @@ import { inspectLibraryPackages } from "./LibraryPackageInventory.js";
import { elfImage } from "../domain/desktop/elfImage.fixture.js";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
describe.skipIf(process.platform === "win32")(
"literal ELF origin paths",
() => {
it.each(["rpath", "runpath"] as const)(
"resolves %s libraries when the executable's directory contains dollars",
async (tag) => {
const root = await createTestTempDirectory("rea-library-origin-");
const bin = join(root, "$build-$&", "bin");
const lib = join(root, "$build-$&", "lib");
await Promise.all([
mkdir(bin, { recursive: true }),
mkdir(lib, { recursive: true }),
]);
const executable = join(bin, "app");
const library = join(lib, "liborigin.so");
await Promise.all([
writeFile(
executable,
elfImage({ needed: ["liborigin.so"], [tag]: "${ORIGIN}/../lib" }),
),
writeFile(library, elfImage({})),
]);
const queried: string[] = [];
const result = await inspectLibraryPackages(
executable,
"app",
async (command, args) => {
if (command === "ldconfig") return { stdout: "", exitCode: 0 };
queried.push(...args.slice(1));
return { stdout: `origin-owner: ${library}`, exitCode: 0 };
},
undefined,
);
expect(result).toEqual({
library_packages: ["origin-owner"],
missing_libraries: [],
unpackaged_libraries: [],
});
expect(queried).toContain(library);
},
);
},
);
it.skipIf(process.platform === "win32")(
"retains a compatible candidate's package when its declared header size is invalid",
async () => {
@@ -325,6 +325,33 @@ describe("libraryClosure unresolved candidates", () => {
});
describe("libraryClosure search paths", () => {
it("expands ORIGIN literally while leaving unsupported source tokens unresolved", async () => {
const closure = await libraryClosure(
{
path: "/opt/$build-$&/bin/app",
dependencies: elf(
["liborigin.so", "libunsupported.so"],
["$ORIGIN/../lib", "$ORIGIN/$LIB", "$ORIGIN_SUFFIX", "relative"],
),
},
[],
host({
"/opt/$build-$&/lib/liborigin.so": elf([]),
"/opt/$build-$&/bin/$LIB/libunsupported.so": elf([]),
"/opt/$build-$&/bin_SUFFIX/libunsupported.so": elf([]),
}),
10,
);
expect(closure).toEqual({
libraries: [
{ soname: "liborigin.so", path: "/opt/$build-$&/lib/liborigin.so" },
],
missing: ["libunsupported.so"],
unreadable: [],
truncated: false,
});
});
it("normalizes $ORIGIN/.. before telling the app's own folder from system libraries", async () => {
const closure = await libraryClosure(
{
+6 -4
View File
@@ -66,16 +66,18 @@ interface LoadedObject {
readonly loader: LoadedObject | undefined;
}
const ORIGIN_TOKEN = /\$(?:ORIGIN(?![A-Za-z0-9_])|\{ORIGIN\})/gu;
/** Absolute directories with `$ORIGIN` expanded to the owner's folder; relative and other dynamic-token paths are left out. */
const expanded = (
owner: string,
directories: readonly string[],
): readonly string[] =>
directories
.map((path) =>
path.replace(/\$(?:ORIGIN|\{ORIGIN\})/gu, posix.dirname(owner)),
)
.filter((path) => path.startsWith("/") && !path.includes("$"))
// Check tokens before expansion: dollars in the owner's directory are data.
.filter((path) => !path.replace(ORIGIN_TOKEN, "").includes("$"))
.map((path) => path.replace(ORIGIN_TOKEN, () => posix.dirname(owner)))
.filter((path) => path.startsWith("/"))
.map((path) => posix.normalize(path));
/**