fix(packaging): validate Flatpak launcher ELF

This commit is contained in:
4gray committed 2026-07-18 19:42:56 +02:00
1 parent 52e248ebf5
commit 1a5ab50872
4 files changed
+165 -6

No files matched your search

@@ -308,6 +308,49 @@ test('package layout verifier uses canonical helpers and direct dependencies', (
assert.match(packageLayoutVerifier, /dirArch !== 'x64'/);
assert.doesNotMatch(packageLayoutVerifier, /getEmbeddedMpvAddonArch/);
assert.match(electronAfterPackSource, /targetArch !== 'x64'/);
assert.match(
packageLayoutVerifier,
/const\s*{\s*resolveLinuxLauncherLayout\s*}\s*=\s*require\(['"]\.\/linux-launcher-layout\.cjs['"]\)/
);
assert.match(
packageLayoutVerifier,
/function verifyLinuxLauncher\(\s*resourceDir,\s*targetNames,\s*errors\s*\)/
);
assert.match(
packageLayoutVerifier,
/resolveLinuxLauncherLayout\(\s*targetNames,\s*linuxExecutableName\s*\)/
);
assert.match(
packageLayoutVerifier,
/verifyLinuxLauncher\(\s*resourceDir,\s*linuxTargetNames,\s*errors\s*\)/
);
assert.match(packageLayoutVerifier, /const elfMagic = Buffer\.alloc\(4\)/);
assert.match(
packageLayoutVerifier,
/fs\.openSync\(launcherPath,\s*['"]r['"]\)/
);
assert.match(
packageLayoutVerifier,
/fs\.readSync\(\s*descriptor,\s*elfMagic,\s*0,\s*elfMagic\.length,\s*0\s*\)/
);
assert.match(packageLayoutVerifier, /fs\.closeSync\(descriptor\)/);
const launcherVerifier = packageLayoutVerifier.match(
/function verifyLinuxLauncher\([\s\S]*?\n}\n\nfunction verifyFlatpakPermissions/
)?.[0];
assert.ok(launcherVerifier);
assert.ok(
launcherVerifier.indexOf('resolveLinuxLauncherLayout(') <
launcherVerifier.indexOf('const launcherBinaryPath')
);
assert.match(
launcherVerifier,
/if \(!launcherLayout\.wrapperRequired\) \{[\s\S]*?return;[\s\S]*?\}\s*const launcherBinaryPath[\s\S]*?fs\.readFileSync\(launcherPath,\s*['"]utf8['"]\)/
);
assert.match(
launcherVerifier,
/fs\.existsSync\(flatpakBinarySiblingPath\)/
);
});
test('nx-electron packaging does not copy duplicate root package metadata', () => {
@@ -279,6 +279,7 @@ function pureValidationOptions(options = {}) {
function validElfInspector(nativeDir, manifest, overrides = {}) {
const libDir = join(nativeDir, 'lib');
const records = new Map([
['iptvnator', { needed: ['libc.so.6'], rpath: [], runpath: [] }],
['iptvnator.bin', { needed: ['libc.so.6'], rpath: [], runpath: [] }],
[
FRAME_COPY_ARTIFACTS.addon,
@@ -598,6 +599,10 @@ test('prepares a normalized system profile with no private runtime', (t) => {
test('prepares portable and Flatpak manifests with the exact bundled closure', (t) => {
const portable = createNativeFixture();
const flatpak = createNativeFixture();
fs.renameSync(
join(flatpak.appOutDir, 'iptvnator.bin'),
join(flatpak.appOutDir, 'iptvnator')
);
t.after(() => {
for (const fixture of [portable, flatpak]) {
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true });
@@ -699,6 +704,11 @@ test('prepares portable and Flatpak manifests with the exact bundled closure', (
pureValidationOptions({
profile: 'flatpak',
targetNames: ['flatpak'],
hostPlatform: 'linux',
elfInspector: validElfInspector(
flatpak.nativeDir,
flatpakManifest
),
})
),
[]
+30 -3
View File
@@ -25,6 +25,7 @@ const {
resolveLinuxFrameCopyProfile,
validateLinuxProfileTargets,
} = require('./linux-frame-copy-profile.cjs');
const { resolveLinuxLauncherLayout } = require('./linux-launcher-layout.cjs');
const forbiddenRuntimePathPrefixes = ['/opt/homebrew/', '/usr/local/'];
const systemRuntimePathPrefixes = ['/System/Library/', '/usr/lib/'];
@@ -1380,9 +1381,26 @@ function inspectLinuxElfIsolation(
resourceDir,
nativeDir,
manifest,
targetNames,
options,
errors
) {
const executableName = options.executableName ?? 'iptvnator';
let launcherLayout;
try {
launcherLayout = resolveLinuxLauncherLayout(
targetNames,
executableName
);
} catch (error) {
errors.push(
`Unable to resolve Linux launcher layout: ${
error instanceof Error ? error.message : String(error)
}`
);
return;
}
const hostPlatform = options.hostPlatform ?? process.platform;
let inspectElf = options.elfInspector;
if (!inspectElf) {
@@ -1403,9 +1421,11 @@ function inspectLinuxElfIsolation(
return;
}
const executableName = options.executableName ?? 'iptvnator';
const inspectedPaths = {
electron: path.join(path.dirname(resourceDir), `${executableName}.bin`),
electron: path.join(
path.dirname(resourceDir),
launcherLayout.electronBinaryName
),
addon: path.join(nativeDir, linuxFrameCopyArtifacts.addon.name),
reader: path.join(nativeDir, linuxFrameCopyArtifacts.frameReader.name),
helper: path.join(nativeDir, linuxFrameCopyArtifacts.helper.name),
@@ -1770,7 +1790,14 @@ function validateLinuxPackagedEmbeddedMpv(resourceDir, options) {
} else {
validateBundledLinuxRuntime(nativeDir, manifest, errors);
}
inspectLinuxElfIsolation(resourceDir, nativeDir, manifest, options, errors);
inspectLinuxElfIsolation(
resourceDir,
nativeDir,
manifest,
targetNames,
options,
errors
);
return errors;
}
@@ -18,6 +18,7 @@ const {
const {
validateLinuxProfileTargets,
} = require('./linux-frame-copy-profile.cjs');
const { resolveLinuxLauncherLayout } = require('./linux-launcher-layout.cjs');
const args = process.argv.slice(2);
const normalizedArgs = args[0] === '--' ? args.slice(1) : args;
const [platform, arch = ''] = normalizedArgs;
@@ -468,10 +469,88 @@ function verifyPackagedPackageMetadata(resourceDir, errors) {
}
}
function verifyLinuxLauncher(resourceDir, errors) {
function verifyLinuxLauncher(resourceDir, targetNames, errors) {
let launcherLayout;
try {
launcherLayout = resolveLinuxLauncherLayout(
targetNames,
linuxExecutableName
);
} catch (error) {
errors.push(
`Unable to resolve Linux launcher layout: ${
error instanceof Error ? error.message : String(error)
}`
);
return;
}
const appDir = path.dirname(resourceDir);
const launcherPath = path.join(appDir, linuxExecutableName);
const launcherBinaryPath = `${launcherPath}.bin`;
if (!launcherLayout.wrapperRequired) {
if (!fileExists(launcherPath)) {
errors.push(
`Missing Flatpak Electron ELF in ${appDir}: ${path.basename(launcherPath)}`
);
return;
}
const flatpakBinarySiblingPath = `${launcherPath}.bin`;
if (fs.existsSync(flatpakBinarySiblingPath)) {
errors.push(
`Flatpak Electron layout must not include a launcher binary sibling: ${flatpakBinarySiblingPath}`
);
}
const expectedElfMagic = Buffer.from([0x7f, 0x45, 0x4c, 0x46]);
const elfMagic = Buffer.alloc(4);
let descriptor;
try {
descriptor = fs.openSync(launcherPath, 'r');
const bytesRead = fs.readSync(
descriptor,
elfMagic,
0,
elfMagic.length,
0
);
if (
bytesRead !== expectedElfMagic.length ||
!elfMagic.equals(expectedElfMagic)
) {
errors.push(
`Flatpak Electron launcher must be an ELF binary: ${launcherPath}`
);
}
} catch (error) {
errors.push(
`Unable to inspect Flatpak Electron ELF at ${launcherPath}: ${
error instanceof Error ? error.message : String(error)
}`
);
} finally {
if (descriptor !== undefined) {
try {
fs.closeSync(descriptor);
} catch (error) {
errors.push(
`Unable to close Flatpak Electron ELF at ${launcherPath}: ${
error instanceof Error
? error.message
: String(error)
}`
);
}
}
}
return;
}
const launcherBinaryPath = path.join(
appDir,
launcherLayout.electronBinaryName
);
if (!fileExists(launcherBinaryPath)) {
errors.push(
@@ -733,7 +812,7 @@ function verifyResourceDir(resourceDir) {
verifyLinuxExecutableArgs(errors);
verifyFlatpakPermissions(errors);
verifySnapPackagingConfig(errors);
verifyLinuxLauncher(resourceDir, errors);
verifyLinuxLauncher(resourceDir, linuxTargetNames, errors);
}
errors.push(