fix: AppImage required --no-sandbox
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Failing after 10m14s
Queue Release Build / build-windows (push) Failing after 15m12s
Queue Release Build / build-linux (push) Successful in 43m41s
Queue Release Build / finalize (push) Skipped
Queue Release Build / build-android (push) Successful in 17m25s
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Failing after 10m14s
Queue Release Build / build-windows (push) Failing after 15m12s
Queue Release Build / build-linux (push) Successful in 43m41s
Queue Release Build / finalize (push) Skipped
Queue Release Build / build-android (push) Successful in 17m25s
Not tested enough but works on my machine
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
mkdtempSync,
|
||||
rmSync,
|
||||
writeFileSync
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import { buildLinuxLauncherScript, resolveLinuxLauncherNames } from './linux-launcher.rules';
|
||||
|
||||
interface KernelFlags {
|
||||
apparmorRestriction: string;
|
||||
unprivilegedUsernsClone: string;
|
||||
maxUserNamespaces: string;
|
||||
}
|
||||
|
||||
const PERMISSIVE_KERNEL: KernelFlags = {
|
||||
apparmorRestriction: '0',
|
||||
unprivilegedUsernsClone: '1',
|
||||
maxUserNamespaces: '15000'
|
||||
};
|
||||
|
||||
let workspace = '';
|
||||
|
||||
function writeKernelFlags(flags: KernelFlags): Record<keyof KernelFlags, string> {
|
||||
const paths = {
|
||||
apparmorRestriction: join(workspace, 'apparmor_restrict_unprivileged_userns'),
|
||||
unprivilegedUsernsClone: join(workspace, 'unprivileged_userns_clone'),
|
||||
maxUserNamespaces: join(workspace, 'max_user_namespaces')
|
||||
};
|
||||
|
||||
for (const key of Object.keys(paths) as (keyof KernelFlags)[]) {
|
||||
writeFileSync(paths[key], `${flags[key]}\n`, 'utf8');
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function runLauncher(flags: KernelFlags, args: string[] = []): string {
|
||||
const paths = writeKernelFlags(flags);
|
||||
const { launcherFileName, binaryFileName } = resolveLinuxLauncherNames('toju');
|
||||
const launcherPath = join(workspace, launcherFileName);
|
||||
const binaryPath = join(workspace, binaryFileName);
|
||||
|
||||
writeFileSync(binaryPath, '#!/bin/sh\nprintf \'%s\\n\' "$@"\n', { encoding: 'utf8', mode: 0o755 });
|
||||
writeFileSync(
|
||||
launcherPath,
|
||||
buildLinuxLauncherScript({
|
||||
binaryFileName,
|
||||
apparmorRestrictionPath: paths.apparmorRestriction,
|
||||
unprivilegedUsernsClonePath: paths.unprivilegedUsernsClone,
|
||||
maxUserNamespacesPath: paths.maxUserNamespaces
|
||||
}),
|
||||
{ encoding: 'utf8', mode: 0o755 }
|
||||
);
|
||||
|
||||
return execFileSync(launcherPath, args, { encoding: 'utf8' }).trim();
|
||||
}
|
||||
|
||||
describe('buildLinuxLauncherScript', () => {
|
||||
beforeEach(() => {
|
||||
workspace = mkdtempSync(join(tmpdir(), 'toju-launcher-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(workspace, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it('keeps the sandbox on when the kernel allows unprivileged user namespaces', () => {
|
||||
expect(runLauncher(PERMISSIVE_KERNEL)).toBe('');
|
||||
});
|
||||
|
||||
it('disables the sandbox when AppArmor confines unprivileged user namespaces', () => {
|
||||
const output = runLauncher({ ...PERMISSIVE_KERNEL, apparmorRestriction: '1' });
|
||||
|
||||
expect(output).toBe('--no-sandbox');
|
||||
});
|
||||
|
||||
it('disables the sandbox when the kernel forbids unprivileged namespace cloning', () => {
|
||||
const output = runLauncher({ ...PERMISSIVE_KERNEL, unprivilegedUsernsClone: '0' });
|
||||
|
||||
expect(output).toBe('--no-sandbox');
|
||||
});
|
||||
|
||||
it('disables the sandbox when no user namespaces are available at all', () => {
|
||||
const output = runLauncher({ ...PERMISSIVE_KERNEL, maxUserNamespaces: '0' });
|
||||
|
||||
expect(output).toBe('--no-sandbox');
|
||||
});
|
||||
|
||||
it('forwards launch arguments to the real binary', () => {
|
||||
const output = runLauncher(PERMISSIVE_KERNEL, ['toju://invite/abc', '--ozone-platform=wayland']);
|
||||
|
||||
expect(output.split('\n')).toEqual(['toju://invite/abc', '--ozone-platform=wayland']);
|
||||
});
|
||||
|
||||
it('never repeats a sandbox switch the caller already supplied', () => {
|
||||
const output = runLauncher(
|
||||
{ ...PERMISSIVE_KERNEL, apparmorRestriction: '1' },
|
||||
['--no-sandbox', '%U']
|
||||
);
|
||||
|
||||
expect(output.split('\n')).toEqual(['--no-sandbox', '%U']);
|
||||
});
|
||||
|
||||
it('assumes a blocked sandbox is fine when the kernel switches are unreadable', () => {
|
||||
const { launcherFileName, binaryFileName } = resolveLinuxLauncherNames('toju');
|
||||
const launcherPath = join(workspace, launcherFileName);
|
||||
|
||||
writeFileSync(
|
||||
join(workspace, binaryFileName),
|
||||
'#!/bin/sh\nprintf \'%s\\n\' "$@"\n',
|
||||
{ encoding: 'utf8', mode: 0o755 }
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
launcherPath,
|
||||
buildLinuxLauncherScript({
|
||||
binaryFileName,
|
||||
apparmorRestrictionPath: join(workspace, 'missing-apparmor'),
|
||||
unprivilegedUsernsClonePath: join(workspace, 'missing-clone'),
|
||||
maxUserNamespacesPath: join(workspace, 'missing-max')
|
||||
}),
|
||||
{ encoding: 'utf8', mode: 0o755 }
|
||||
);
|
||||
|
||||
expect(execFileSync(launcherPath, { encoding: 'utf8' }).trim()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveLinuxLauncherNames', () => {
|
||||
it('keeps the published executable name for the launcher and renames the binary', () => {
|
||||
expect(resolveLinuxLauncherNames('toju')).toEqual({
|
||||
launcherFileName: 'toju',
|
||||
binaryFileName: 'toju-bin'
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user