Files
orca/config/scripts/node-pty-job-ownership.cjs
T

199 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const { existsSync, readFileSync } = require('node:fs')
const { dirname, join, resolve } = require('node:path')
const { PE_MACHINE, describePeMachine, readPeMachine } = require('./windows-pe-machine.cjs')
const NODE_PTY_JOB_EXPORTS = [
'listJobProcessIds',
'terminateJob',
'assignCurrentProcessToJob',
'assignCurrentProcessToStrictJob'
]
const NATIVE_HOST_JOB_MARKER_TEXT = 'assignCurrentProcessToStrictJob'
const NATIVE_HOST_JOB_MARKER = Buffer.from(NATIVE_HOST_JOB_MARKER_TEXT)
// Inspect usesCygwinRuntime’s UTF-16 marker: job exports predate MSYS breakaway denial and pass on builds that leak Git Bash children.
// See docs/reference/windows-msys-job-breakaway.md; windows-process-table.ts uses the same binary-marker strategy.
const CYGWIN_BREAKAWAY_MARKER_TEXT = 'msys-2.0.dll'
const CYGWIN_BREAKAWAY_MARKER = Buffer.from(CYGWIN_BREAKAWAY_MARKER_TEXT, 'utf16le')
// Propagate read failures: an unreadable addon must never pass the denial check.
function conptyDeniesCygwinBreakaway(addonPath) {
return readFileSync(addonPath).includes(CYGWIN_BREAKAWAY_MARKER)
}
const NODE_PTY_CONPTY_SOURCE = join('src', 'win', 'conpty.cc')
// Reject stale source before compiling: rebuilding an old install cannot add the denial; pnpm install must apply the current patch.
// Missing source is left to the post-build binary check.
function assertNodePtySourceDeniesMsysBreakaway({ nodePtyDir }) {
const sourcePath = join(nodePtyDir, NODE_PTY_CONPTY_SOURCE)
if (!existsSync(sourcePath)) {
return
}
const source = readFileSync(sourcePath, 'utf8')
if (source.includes(`L"${CYGWIN_BREAKAWAY_MARKER_TEXT}"`)) {
if (!source.includes(`"${NATIVE_HOST_JOB_MARKER_TEXT}"`)) {
throw new Error(
`node-pty's source at ${sourcePath} lacks strict native-host process ownership; ` +
'run `pnpm install` to apply the current patch before rebuilding.'
)
}
return
}
throw new Error(
[
`node-pty's source at ${sourcePath} does not carry the Cygwin/MSYS job-breakaway denial,`,
'so no rebuild of it can produce an addon that does; the compile was not started.',
'pnpm applies config/patches/node-pty@1.1.0.patch when it installs node-pty, so run',
'`pnpm install` to re-materialize it with the current patch, then rerun this command.',
`If that patch does not itself add L"${CYGWIN_BREAKAWAY_MARKER_TEXT}" to ${NODE_PTY_CONPTY_SOURCE},`,
'this checkout predates the denial and no reinstall can supply it.',
'See docs/reference/windows-msys-job-breakaway.md.'
].join(' ')
)
}
// Check target PE architecture immediately: wrong-arch addons fall back to an unpatched prebuild; unknown architectures remain unjudged.
function assertRebuiltConptyMatchesArch(addonPath, rebuildArch) {
const expected = PE_MACHINE[rebuildArch]
if (expected === undefined) {
return
}
const machine = readPeMachine(addonPath)
if (machine === expected) {
return
}
const consequence = [
', so node-pty would fall back to the published prebuild, which predates the',
'Cygwin/MSYS job-breakaway denial and leaks every MSYS pane child out of its job.'
].join(' ')
throw new Error(
machine === null
? `${addonPath} is not a PE image${consequence} Check the ` +
`node-pty build output above; a truncated or quarantined artifact looks like this.`
: `${addonPath} is ${describePeMachine(machine)}, but this rebuild targets ` +
`win32-${rebuildArch} (0x${expected.toString(16)}): node-gyp did not honour ` +
`--arch${consequence}`
)
}
// Inject host state to test same-host behavior on any OS. Missing same-host output is fatal because loader falls back to unsafe upstream prebuild; cross-host or absent package only warns.
function assertRebuiltConptyDeniesMsysBreakaway({
nodePtyDir,
rebuildArch,
crossHost,
warn = console.warn
}) {
const addonPath = join(nodePtyDir, 'build', 'Release', 'conpty.node')
if (existsSync(addonPath)) {
assertRebuiltConptyMatchesArch(addonPath, rebuildArch)
assertCygwinBreakawayDenied(addonPath, { dir: addonPath })
assertNativeHostJobOwnership(addonPath)
return
}
if (crossHost || !existsSync(nodePtyDir)) {
warn(`[rebuild] no addon at ${addonPath}; could not check the MSYS job-breakaway denial.`)
return
}
const prebuildPath = join(nodePtyDir, 'prebuilds', `win32-${rebuildArch}`, 'conpty.node')
throw new Error(
`the rebuild reported success but ${addonPath} is not there, so node-pty would fall through ` +
`to ${prebuildPath}. That published prebuild predates the Cygwin/MSYS ` +
'job-breakaway denial: every Git Bash pane child would be created outside its job and ' +
'survive terminatePtyJob. Check the node-pty build output above; a same-host source ' +
'build must leave conpty.node in build/Release.'
)
}
// Resolve native.dir against node-pty’s lib/, the common base across installed, staged and packaged roots.
function nodePtyAddonPath(nodePtyUtilsPath, native, nativeName) {
return resolve(dirname(nodePtyUtilsPath), native.dir, `${nativeName}.node`)
}
function assertNodePtyJobOwnership({ nativeName, native, addonPath, platform = process.platform }) {
if (platform !== 'win32' || nativeName !== 'conpty') {
return
}
const exported = native?.module ?? native
const missing = NODE_PTY_JOB_EXPORTS.filter((name) => typeof exported?.[name] !== 'function')
if (missing.length > 0) {
throw new Error(
[
`node-pty's conpty native is missing ${missing.join(', ')}.`,
`Resolved from: ${native?.dir ?? 'unknown'}`,
'That build cannot own a PTY tree, so terminatePtyJob degrades to "unavailable"',
'and pane teardown falls back to guessing by PID ancestry.',
'Rebuild node-pty from source so config/patches/node-pty@1.1.0.patch applies.'
].join(' ')
)
}
assertCygwinBreakawayDenied(addonPath, native)
assertNativeHostJobOwnership(addonPath)
}
/** The export name is embedded by N-API and can be checked across target architectures. */
function assertNativeHostJobOwnership(addonPath) {
if (readFileSync(addonPath).includes(NATIVE_HOST_JOB_MARKER)) {
return
}
throw new Error(
`node-pty's conpty native at ${addonPath} lacks strict native-host process ownership; ` +
'reinstall the current node-pty patch and rebuild before packaging.'
)
}
// Fail closed on unreadable addon: loadNativeModule already loaded it, so a missing path means the gate cannot inspect its subject.
function assertCygwinBreakawayDenied(addonPath, native) {
let binary
try {
binary = readFileSync(addonPath)
} catch (error) {
throw new Error(
[
`Cannot read node-pty's conpty native at ${addonPath ?? '<no path given>'}`,
`(resolved from ${native?.dir ?? 'unknown'}): ${error.message}.`,
'Without the binary this cannot tell a current build from one that leaks',
'every MSYS pane child out of its job, so it refuses rather than assume.'
].join(' ')
)
}
if (binary.includes(CYGWIN_BREAKAWAY_MARKER)) {
return
}
throw staleConptySourceBuildError(addonPath)
}
function staleConptySourceBuildError(addonPath) {
return new Error(
[
`node-pty's conpty native at ${addonPath} predates the Cygwin/MSYS job-breakaway denial.`,
'It exports the job functions, so it looks patched, but its per-PTY job still carries',
'JOB_OBJECT_LIMIT_BREAKAWAY_OK and every Git Bash child is created outside the job:',
'terminatePtyJob reports "terminated" and leaves the tree running.',
'Rebuild node-pty from source so the current config/patches/node-pty@1.1.0.patch applies',
'(a worktree sharing node_modules with its main checkout shares that stale addon).',
`If that patch no longer adds L"${CYGWIN_BREAKAWAY_MARKER_TEXT}" to conpty.cc then this marker is`,
'stale, not the addon, and no rebuild can satisfy it.',
'See docs/reference/windows-msys-job-breakaway.md.'
].join(' ')
)
}
module.exports = {
CYGWIN_BREAKAWAY_MARKER,
CYGWIN_BREAKAWAY_MARKER_TEXT,
NATIVE_HOST_JOB_MARKER,
NATIVE_HOST_JOB_MARKER_TEXT,
assertNativeHostJobOwnership,
assertNodePtyJobOwnership,
assertCygwinBreakawayDenied,
assertNodePtySourceDeniesMsysBreakaway,
assertRebuiltConptyMatchesArch,
assertRebuiltConptyDeniesMsysBreakaway,
conptyDeniesCygwinBreakaway,
nodePtyAddonPath,
staleConptySourceBuildError
}