Admit stable verified runner links in Pi provider-death fixture

This commit is contained in:
Dotta committed 2026-10-05 16:28:24 -05:00
1 parent 660c6f63f6
commit bfd0292f5e
5 files changed
+79 -6

No files matched your search

+11
View File
@@ -386,6 +386,17 @@ or `/proc/self/fd/7` in the wrapper argv. That form is admitted only when the
wrapper's corresponding descriptor and executable both have the exact sealed
snapshot Node inode. The guard and wrapper entrypoint paths remain exact.
Missing, foreign or other descriptor numbers fail before signalling.
Production may also stage the runner executable as a link to the image's
verified installation. The fault helper reads the resolved regular file, checks
that the named link remained unchanged, and still requires its pinned hash and
exact `/proc/<runner-pid>/exe` inode. Link replacement, missing targets and a
different executable fail admission. Linux calibration covers this installation
form as well as a copied runner.
The agent-memory fixture treats its quoted JSON string as UTF-8 file content,
including a final line-feed byte (`0x0A`). Native readback and the managed-file
API must retain those exact bytes across a new task and controller restart.
The prompt makes that decoding explicit; the byte graders remain unchanged.
The runtime itself must emit `runtime_request.expired` for the original callback
with `provider_process_lost` and `replayAllowed:false`, followed by native turn
+1 -1
View File
@@ -1114,7 +1114,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => profile.qualificationCandidate === "pi"),
environments: runnerEnvironments, tasks: piNativeTasks, expectedMatrixSize: 10,
excludedExecutionIds: ["pi-native.runner-acpx-pi.daytona.restrictive-denial", "pi-native.runner-acpx-pi.local.native-pending-provider-death"],
definitionMetadata: { version: 7, qualification: "pending", scheduling: "explicit-only", profileVersion: QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, agentMemoryContent: "utf8-nonce-plus-final-lf", providerDeath: "daytona-exact-pi-child-pidfd-production-expiry", providerFaultExecutable: "snapshot-node-inode-with-held-bootstrap-fd-3-or-7", remoteBootstrapApproval: "exact-published-native-read-public-accept-once-v1", remoteDenyAll: "unsupported-native-bootstrap-read-is-denied", remoteEvidence: "owned-lease-sealed-observer", pendingControllerRestart: "same-live-native-request" },
definitionMetadata: { version: 8, qualification: "pending", scheduling: "explicit-only", profileVersion: QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, agentMemoryContent: "utf8-nonce-plus-final-lf", providerDeath: "daytona-exact-pi-child-pidfd-production-expiry", providerFaultExecutable: "stable-preinstalled-runner-link-and-snapshot-node-inode-with-held-bootstrap-fd-3-or-7", remoteBootstrapApproval: "exact-published-native-read-public-accept-once-v1", remoteDenyAll: "unsupported-native-bootstrap-read-is-denied", remoteEvidence: "owned-lease-sealed-observer", pendingControllerRestart: "same-live-native-request" },
},
{
id: "native-active-stop", label: "Stop an unanswered native permission", manualOnly: true,
+15 -1
View File
@@ -72,6 +72,20 @@ def digest(data):
return hashlib.sha256(data).hexdigest()
def checked_runner_executable(path):
# Production stages a verified preinstalled runner with ln -sfn. Admit
# that named link only while its identity and resolved regular file remain
# stable; inspect still requires both the pinned hash and /proc/exe inode.
before = os.lstat(path)
require(stat.S_ISREG(before.st_mode) or stat.S_ISLNK(before.st_mode), 'runner_file_shape')
resolved = os.path.realpath(path)
content, inode = checked_file(resolved, 256 * 1024 * 1024)
after = os.lstat(path)
identity = lambda s: (s.st_dev, s.st_ino, s.st_mode, s.st_mtime_ns, s.st_ctime_ns)
require(identity(before) == identity(after) and os.path.realpath(path) == resolved, 'runner_link_changed')
return content, inode
def parse_closure_metadata(content, expected_pin):
# Match production parseNativeAcpxDistributionEntries: the profile pins
# canonical entries, not the formatting or outer JSON file bytes.
@@ -111,7 +125,7 @@ def inspect(config):
and flag(root_args, '--environment-lease-id') == config['runtimeEnvironmentLeaseId']
and flag(root_args, '--lifecycle-mode') == 'per_turn', 'root_binding')
require(re.fullmatch(re.escape(runtime_root) + r'/sessions/[a-f0-9]{64}/runner', flag(root_args, '--state-dir')), 'root_session')
runner_bytes, runner_inode = checked_file(runner, 256 * 1024 * 1024)
runner_bytes, runner_inode = checked_runner_executable(runner)
executable = os.stat(f'/proc/{root["pid"]}/exe')
require((executable.st_dev, executable.st_ino) == runner_inode and 'sha256:' + digest(runner_bytes) == config['runnerdSha256'], 'runner_executable')
closure_bytes, _ = checked_file(PACK + '/provider-assets/pi/linux-x64/native-closure.json', 4 * 1024 * 1024)
+50 -2
View File
@@ -98,6 +98,7 @@ class IdentityTests(unittest.TestCase):
self.args = {21: [RUNTIME + '/bin/paperclip-runnerd', '--run-id', 'run', '--environment-lease-id', 'workspace-id', '--lifecycle-mode', 'per_turn', '--state-dir', RUNTIME + '/sessions/' + 'a' * 64 + '/runner'],
22: [DIST + '/' + fault.NODE, '--require', DIST + '/' + fault.GUARD, DIST + '/pi-entry.cjs'], 23: ['pi']}
self.patches = [patch.object(fault, 'proc', side_effect=lambda pid, boot: self.table[pid]), patch.object(fault, 'argv', side_effect=lambda pid: self.args[pid]),
patch.object(fault, 'checked_runner_executable', side_effect=lambda p: self.files[p]),
patch.object(fault, 'checked_file', side_effect=lambda p, *a: self.files[p]), patch.object(Path, 'read_text', return_value=BOOT),
patch.object(os, 'listdir', return_value=['21', '22', '23']), patch.object(os, 'getpgid', return_value=21),
patch.object(os, 'lstat', return_value=SimpleNamespace(st_mode=0o40500)), patch.object(os.path, 'realpath', side_effect=lambda p: p),
@@ -158,6 +159,43 @@ class IdentityTests(unittest.TestCase):
with patch.object(os, 'listdir', return_value=['21', '22', '23', '24']), self.assertRaisesRegex(RuntimeError, 'unique_pi_child'): fault.inspect(self.config)
class RunnerExecutableTests(unittest.TestCase):
def test_regular_and_preinstalled_link_resolve_to_the_same_inode(self):
with tempfile.TemporaryDirectory() as tmp:
executable = Path(tmp).resolve() / 'installed-runner'
executable.write_bytes(b'pinned runner')
link = executable.with_name('runtime-runner')
link.symlink_to(executable)
content, inode = fault.checked_runner_executable(str(link))
self.assertEqual(content, b'pinned runner')
self.assertEqual((content, inode), fault.checked_runner_executable(str(executable)))
def test_retargeted_link_is_rejected_after_read(self):
with tempfile.TemporaryDirectory() as tmp:
executable = Path(tmp).resolve() / 'installed-runner'
executable.write_bytes(b'pinned runner')
foreign = executable.with_name('foreign-runner')
foreign.write_bytes(b'foreign')
link = executable.with_name('runtime-runner')
link.symlink_to(executable)
original = fault.checked_file
def replace_during_read(*args):
result = original(*args)
link.unlink()
link.symlink_to(foreign)
return result
with patch.object(fault, 'checked_file', side_effect=replace_during_read), self.assertRaisesRegex(RuntimeError, 'runner_link_changed'):
fault.checked_runner_executable(str(link))
def test_missing_link_and_directory_never_admit_an_executable(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp).resolve()
link = root / 'runtime-runner'
link.symlink_to(root / 'missing')
with self.assertRaises(FileNotFoundError): fault.checked_runner_executable(str(link))
with self.assertRaisesRegex(RuntimeError, 'runner_file_shape'): fault.checked_runner_executable(str(root))
class PidfdTests(unittest.TestCase):
def test_changed_or_retired_pidfd_never_signals_and_always_closes(self):
before = {'target': TARGET}
@@ -186,7 +224,11 @@ class PidfdTests(unittest.TestCase):
for descriptor in [3, 7]:
with self.subTest(descriptor=descriptor): self.calibrate_real_child(descriptor)
def calibrate_real_child(self, descriptor=None):
@unittest.skipUnless(sys.platform == 'linux' and hasattr(os, 'pidfd_open'), 'Hosted native Linux preinstalled-link calibration required')
def test_real_preinstalled_runner_link_then_exact_child_pidfd(self):
self.calibrate_real_child(7, runner_link=True)
def calibrate_real_child(self, descriptor=None, runner_link=False):
node = shutil.which('node'); self.assertIsNotNone(node)
base = Path(tempfile.mkdtemp(prefix='pi-fault-calibration-', dir='/tmp'))
snapshot = Path(tempfile.mkdtemp(prefix='paperclip-acpx-native-', dir='/tmp'))
@@ -199,7 +241,13 @@ class PidfdTests(unittest.TestCase):
for name in NAMES + [fault.GUARD]:
p = distribution / name; p.parent.mkdir(parents=True, exist_ok=True); p.write_text('// fixture\n')
shutil.copyfile(node, distribution / fault.NODE); (distribution / fault.NODE).chmod(0o500)
shutil.copyfile(node, runtime / 'bin/paperclip-runnerd'); (runtime / 'bin/paperclip-runnerd').chmod(0o500)
runner = runtime / 'bin/paperclip-runnerd'
if runner_link:
installed = base / 'preinstalled-runner'
shutil.copyfile(node, installed); installed.chmod(0o500)
runner.symlink_to(installed)
else:
shutil.copyfile(node, runner); runner.chmod(0o500)
ready = workspace / 'child.json'; exited = workspace / 'exit.json'
(distribution / fault.ENTRY).write_text('process.title="pi"; require("node:fs").writeFileSync(' + json.dumps(str(ready)) + ',JSON.stringify({pid:process.pid}));setInterval(()=>{},1000);')
(distribution / 'pi-entry.cjs').write_text('const p=require("node:child_process").spawn(process.execPath,["--require",' + json.dumps(str(distribution / fault.GUARD)) + ',' + json.dumps(str(distribution / fault.ENTRY)) + '],{stdio:"ignore"});p.on("exit",(code,signal)=>require("node:fs").writeFileSync(' + json.dumps(str(exited)) + ',JSON.stringify({code,signal})));process.on("SIGTERM",()=>{if(p.exitCode!==null||p.signalCode!==null)process.exit(0);p.once("exit",()=>process.exit(0));p.kill("SIGTERM")});setInterval(()=>{},1000);')
+2 -2
View File
@@ -15,8 +15,8 @@ it("calibrates the exact Pi fault helper with metadata negatives and native Linu
expect(result.error).toBeUndefined();
expect(result.signal).toBeNull();
expect(result.status, result.stderr).toBe(0);
expect(result.stderr).toContain("Ran 17 tests");
expect(result.stderr).toContain("Ran 21 tests");
if (process.platform === "linux") expect(result.stderr).not.toContain("skipped");
else expect(result.stderr).toContain("skipped=2");
else expect(result.stderr).toContain("skipped=3");
console.info(result.stderr.trim());
}, 30_000);