launcher.test.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /**
  2. * Behavioral tests against the REAL launcher binary on a real kernel: the
  3. * CLI contract (usage errors, exit codes, argv passthrough) and the
  4. * confinement world-proofs (denied writes stay off disk, grants land).
  5. *
  6. * Preconditions and their skip semantics:
  7. * - Non-Linux host: skips entirely (exit 0) — there is nothing to build here.
  8. * - Linux without the built binary: FAILS — run `pnpm build:native` first.
  9. * - Linux whose kernel does not enforce Landlock: skips the enforcement
  10. * half, unless `NALR_REQUIRE_LANDLOCK=1` (set on CI, where a silent skip on
  11. * the very platform that exists to prove enforcement would be a false
  12. * green).
  13. */
  14. import assert from 'node:assert/strict';
  15. import fs from 'node:fs';
  16. import os from 'node:os';
  17. import path from 'node:path';
  18. import { spawnSync } from 'node:child_process';
  19. import {
  20. LAUNCHER_FAILURE_EXIT,
  21. grantArgs,
  22. launcherPath,
  23. probe,
  24. } from '@deepseek-ai/node-addon-system/landlock-run';
  25. const FATAL_PREFIX = 'landlock-run: ';
  26. const PARTIAL_NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)';
  27. const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1';
  28. if (process.platform !== 'linux') {
  29. console.log(`launcher.test: SKIP — the launcher only exists on linux (host: ${process.platform})`);
  30. process.exit(0);
  31. }
  32. const launcher = launcherPath();
  33. assert.ok(
  34. fs.existsSync(launcher),
  35. `launcher.test: no built launcher at ${launcher} — run \`pnpm build:native\` (apt-get install musl-tools) first`,
  36. );
  37. const run = (args, options = {}) => spawnSync(launcher, args, { encoding: 'utf8', ...options });
  38. // --- usage errors: parse failures exit LAUNCHER_FAILURE_EXIT before any restriction ---
  39. {
  40. const noCommand = run([]);
  41. assert.equal(noCommand.status, LAUNCHER_FAILURE_EXIT);
  42. assert.ok(noCommand.stderr.startsWith(FATAL_PREFIX));
  43. assert.match(noCommand.stderr, /usage error: missing `-- <argv>\.\.\.` command/);
  44. const unknownFlag = run(['--bogus', '--', 'true']);
  45. assert.equal(unknownFlag.status, LAUNCHER_FAILURE_EXIT);
  46. assert.match(unknownFlag.stderr, /usage error: unknown argument: --bogus/);
  47. const danglingPath = run(['--ro']);
  48. assert.equal(danglingPath.status, LAUNCHER_FAILURE_EXIT);
  49. assert.match(danglingPath.stderr, /--ro requires a path/);
  50. for (const args of [
  51. ['--probe', '--ro', '/'],
  52. ['--probe', '--'],
  53. ['--probe', '--probe'],
  54. ]) {
  55. const probeWithExtras = run(args);
  56. assert.equal(probeWithExtras.status, LAUNCHER_FAILURE_EXIT);
  57. assert.match(probeWithExtras.stderr, /--probe takes no other arguments/);
  58. }
  59. }
  60. // --- probe: the functional availability signal ---
  61. const enforcement = probe(launcher);
  62. console.log(`launcher.test: probe → ${enforcement}`);
  63. if (enforcement === 'unusable') {
  64. if (requireLandlock) {
  65. console.error('launcher.test: NALR_REQUIRE_LANDLOCK=1 but the probe reports unusable — this kernel cannot prove enforcement');
  66. process.exit(1);
  67. }
  68. console.log('launcher.test: SKIP enforcement half — kernel does not enforce Landlock');
  69. process.exit(0);
  70. }
  71. const expectedNotice = enforcement === 'partial' ? `${PARTIAL_NOTICE}\n` : '';
  72. {
  73. const probeRun = run(['--probe']);
  74. assert.equal(probeRun.status, 0);
  75. assert.match(probeRun.stdout, /^landlock: (fully enforced|partially enforced \(older ABI\))\n$/);
  76. }
  77. // --- confined exec: the command runs, its exit code passes through ---
  78. {
  79. const echo = run([...grantArgs({ readOnly: ['/'] }), '--', '/bin/sh', '-c', 'echo confined-ok']);
  80. assert.equal(echo.status, 0, echo.stderr);
  81. assert.equal(echo.stdout, 'confined-ok\n');
  82. assert.equal(echo.stderr, expectedNotice);
  83. const exitCode = run([...grantArgs({ readOnly: ['/'] }), '--', '/bin/sh', '-c', 'exit 7']);
  84. assert.equal(exitCode.status, 7, 'the wrapped command exit code must pass through unchanged');
  85. const child125 = run([...grantArgs({ readOnly: ['/'] }), '--', '/bin/sh', '-c', `exit ${LAUNCHER_FAILURE_EXIT}`]);
  86. assert.equal(child125.status, LAUNCHER_FAILURE_EXIT, 'a wrapped child may itself return the launcher failure status');
  87. assert.equal(child125.stderr, expectedNotice);
  88. }
  89. // --- world-proofs: denied writes stay off disk, grants land, inheritance crosses exec ---
  90. {
  91. const work = fs.mkdtempSync(path.join(os.tmpdir(), 'nalr-launcher-test-'));
  92. const denied = path.join(work, 'denied.txt');
  93. const deniedRun = run([...grantArgs({ readOnly: ['/'] }), '--', '/bin/sh', '-c', `echo x > ${denied}`]);
  94. assert.notEqual(deniedRun.status, 0, 'a write outside the grants must fail');
  95. assert.ok(!fs.existsSync(denied), 'the denied write must not land on disk');
  96. const granted = path.join(work, 'granted.txt');
  97. const grantedRun = run([...grantArgs({ readOnly: ['/'], readWrite: [work] }), '--', '/bin/sh', '-c', `echo ok > ${granted}`]);
  98. assert.equal(grantedRun.status, 0, grantedRun.stderr);
  99. assert.equal(fs.readFileSync(granted, 'utf8'), 'ok\n');
  100. // The ruleset is inherited across execve: a CHILD of the wrapped command
  101. // is confined too, not just the direct exec target.
  102. const nested = path.join(work, 'nested.txt');
  103. const nestedRun = run([...grantArgs({ readOnly: ['/'] }), '--', '/bin/sh', '-c', `/bin/sh -c 'echo x > ${nested}'; true`]);
  104. assert.equal(nestedRun.status, 0, nestedRun.stderr);
  105. assert.ok(!fs.existsSync(nested), 'a denied write from a nested child must not land either');
  106. fs.rmSync(work, { recursive: true, force: true });
  107. }
  108. // --- fail closed: an unopenable grant root refuses to exec at all ---
  109. {
  110. const marker = path.join(os.tmpdir(), `nalr-should-not-exist-${process.pid}`);
  111. const badGrant = run(['--ro', '/no/such/grant/root', '--', '/bin/sh', '-c', `echo x > ${marker}`]);
  112. assert.equal(badGrant.status, LAUNCHER_FAILURE_EXIT);
  113. assert.ok(badGrant.stderr.startsWith(FATAL_PREFIX));
  114. assert.match(badGrant.stderr, /cannot open rule path/);
  115. assert.ok(!fs.existsSync(marker), 'the command must never run when the launcher fails');
  116. }
  117. console.log('launcher.test: ok');