ReviewOS

also looking at this

pantry-pm/pantry

fix(chrome): ship NSS's runtime providers and gate on a real render

#246
Open chris wants to merge fix/chrome-nss-runtime-modules into main
3 files +114 -2

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/scripts/linux-native-browser.tsmodified+24-6
Changes to .github/scripts/linux-native-browser.ts
@@ -26,14 +26,18 @@ async function run(name: string, args: string[], timeout = 30_000): Promise<stri
2626 return output
2727}
2828
29/** Run with nothing inherited but HOME and a PATH without the package, so only its own layout resolves libraries. */
30async function runClean(name: string, args: string[], timeout = 60_000): Promise<string> {
29/**
30 * Run with nothing inherited but HOME and a PATH without the package, so only
31 * its own layout resolves libraries. LD_DEBUG=files makes the dynamic loader
32 * report each library it initialises (on stderr) without changing a lookup.
33 */
34async function runClean(name: string, args: string[], timeout = 60_000): Promise<{ stdout: string, stderr: string }> {
3135 const stdout = join(artifacts, `${name}.stdout.log`)
3236 const stderr = join(artifacts, `${name}.stderr.log`)
33 const child = Bun.spawn(args, { cwd: root, env: { HOME: root, PATH: '/usr/bin:/bin' }, stdout: Bun.file(stdout), stderr: Bun.file(stderr), signal: AbortSignal.timeout(timeout) })
37 const child = Bun.spawn(args, { cwd: root, env: { HOME: root, PATH: '/usr/bin:/bin', LD_DEBUG: 'files' }, stdout: Bun.file(stdout), stderr: Bun.file(stderr), signal: AbortSignal.timeout(timeout) })
3438 const code = await child.exited
35 const output = await Bun.file(stdout).text()
36 assert.equal(code, 0, `${name}: ${output}${await Bun.file(stderr).text()}`)
39 const output = { stdout: await Bun.file(stdout).text(), stderr: await Bun.file(stderr).text() }
40 assert.equal(code, 0, `${name}: ${output.stdout}${output.stderr.slice(-4000)}`)
3741 return output
3842}
3943
@@ -77,8 +81,22 @@ try {
7781 const page = join(root, 'render-profile')
7882 await mkdir(page)
7983 const rendered = await runClean('chrome-render', [entry, '--headless=new', '--disable-gpu', '--no-first-run', `--user-data-dir=${page}`, '--dump-dom', 'data:text/html,<h1 id="pantry">rendered by the installed package</h1>'])
80 assert.match(rendered, /<h1 id="pantry">rendered by the installed package<\/h1>/, 'Chrome must render with its own libraries and its sandbox')
84 assert.match(rendered.stdout, /<h1 id="pantry">rendered by the installed package<\/h1>/, 'Chrome must render with its own libraries and its sandbox')
8185 proof.rendered = true
86
87 // A render alone is not proof: a host NSS (GitHub's image ships one with its
88 // own Chrome) answers NSS's dlopen fallback for a provider the package left
89 // out, and the page renders anyway. Require the loader to have initialised
90 // softokn and its crypto provider from the installed package itself.
91 const initialised = [...rendered.stderr.matchAll(/calling init: (\S+)/g)].map(match => match[1]!)
92 const nss = initialised.filter(path => /\/lib(?:softokn3|freeblpriv3)\.so$/.test(path))
93 proof.nssModules = [...new Set(nss)]
94 for (const module of ['libsoftokn3.so', 'libfreeblpriv3.so']) {
95 const loaded = nss.filter(path => path.endsWith(`/${module}`))
96 assert(loaded.length > 0, `Chrome never initialised ${module}`)
97 for (const path of loaded)
98 assert(path.startsWith(`${root}/pantry/`), `${module} came from ${path}, not the installed package`)
99 }
82100 proof.passed = true
83101}
84102finally {
packages/ts-pantry/test/chrome-nss-runtime.test.tsmodified+4-1
Changes to packages/ts-pantry/test/chrome-nss-runtime.test.ts
@@ -25,7 +25,10 @@ test('the Linux release gate renders a page with the installed package, not just
2525 expect(gate).toContain('--dump-dom')
2626 // A clean environment: only HOME and a PATH without the package, so an
2727 // inherited LD_LIBRARY_PATH cannot supply the library the package lacks.
28 expect(gate).toContain(`env: { HOME: root, PATH: '/usr/bin:/bin' }`)
28 expect(gate).toContain(`env: { HOME: root, PATH: '/usr/bin:/bin', LD_DEBUG: 'files' }`)
29 // And a host NSS cannot stand in: the providers must load from the package.
30 expect(gate).toContain('calling init:')
31 expect(gate).toContain('not the installed package')
2932 // The sandbox stays on.
3033 expect(gate).not.toContain("'--no-sandbox'")
3134 expect(gate).toContain('userns,')