From 1a5ab50872c4ae2996a6acd6b3dacc9d3ece13ae Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 18 Jul 2026 19:42:56 +0200 Subject: [PATCH] fix(packaging): validate Flatpak launcher ELF --- .../electron-package-identity.test.mjs | 43 ++++++++++ tools/packaging/embedded-mpv-arch.test.mjs | 10 +++ tools/packaging/embedded-mpv-packaging.cjs | 33 ++++++- .../verify-electron-package-layout.mjs | 85 ++++++++++++++++++- 4 files changed, 165 insertions(+), 6 deletions(-) diff --git a/tools/packaging/electron-package-identity.test.mjs b/tools/packaging/electron-package-identity.test.mjs index 64d934b8d..7dd6207d3 100644 --- a/tools/packaging/electron-package-identity.test.mjs +++ b/tools/packaging/electron-package-identity.test.mjs @@ -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', () => { diff --git a/tools/packaging/embedded-mpv-arch.test.mjs b/tools/packaging/embedded-mpv-arch.test.mjs index ca37b0d02..1b4f56c17 100644 --- a/tools/packaging/embedded-mpv-arch.test.mjs +++ b/tools/packaging/embedded-mpv-arch.test.mjs @@ -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 + ), }) ), [] diff --git a/tools/packaging/embedded-mpv-packaging.cjs b/tools/packaging/embedded-mpv-packaging.cjs index 02e2a0711..173c89b09 100644 --- a/tools/packaging/embedded-mpv-packaging.cjs +++ b/tools/packaging/embedded-mpv-packaging.cjs @@ -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; } diff --git a/tools/packaging/verify-electron-package-layout.mjs b/tools/packaging/verify-electron-package-layout.mjs index ab94e048c..d86e5fec0 100644 --- a/tools/packaging/verify-electron-package-layout.mjs +++ b/tools/packaging/verify-electron-package-layout.mjs @@ -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(