Files
PaperClipAI/tests/runner-e2e/kimi-provider-fault.py
T
DottaandPaperclip 8c9dd1e444 test(runner): qualify Kimi Code lifecycle and recovery (#18029)
Add the Kimi lifecycle and recovery qualification harness. Preserve original grades and cleanup evidence, strict native ownership audits, bounded startup and cleanup, and early rejection of unsupported local fault execution before credentials or resources.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-30 22:54:37 -05:00

271 lines
15 KiB
Python

import base64
import hashlib
import fcntl
import json
import os
from pathlib import Path
import re
import signal
import stat
import sys
PACK = '/opt/paperclip-runner/provider-pack'
ENTRY = 'node_modules/@moonshot-ai/kimi-code/dist/main.mjs'
NODE = 'node/bin/node'
GUARD = '.paperclip-native-module-guard.cjs'
BOOTSTRAP = '.paperclip-native-entry.cjs'
PLACEHOLDER = '/__paperclip_kimi_fault_snapshot__/distribution'
def require(value, code):
if not value:
raise RuntimeError('kimi_provider_fault:' + code)
def digest(data):
return hashlib.sha256(data).hexdigest()
def proc(pid, boot):
raw = Path(f'/proc/{pid}/stat').read_text()
require(raw.startswith(f'{pid} ('), 'stat_pid')
fields = raw.rsplit(') ', 1)[1].split()
require(len(fields) >= 20 and fields[0] != 'Z' and fields[19].isdigit(), 'dead_or_invalid_process')
return {'pid': pid, 'ppid': int(fields[1]), 'startTicks': fields[19], 'bootId': boot}
def argv(pid):
raw = Path(f'/proc/{pid}/cmdline').read_bytes()
require(0 < len(raw) <= 65536, 'argv_bound')
return raw.decode('utf8').rstrip('\0').split('\0')
def flag(args, name):
require(args.count(name) == 1 and args.index(name) + 1 < len(args), 'root_flag')
return args[args.index(name) + 1]
def checked_file(path, maximum, sealed=False):
require(os.path.realpath(path) == path, 'file_realpath')
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
try:
before = os.fstat(fd)
require(stat.S_ISREG(before.st_mode) and before.st_nlink == 1 and 0 < before.st_size <= maximum, 'file_shape')
require(not sealed or before.st_mode & 0o222 == 0, 'file_writable')
chunks, size = [], 0
while True:
chunk = os.read(fd, 1024 * 1024)
if not chunk:
break
chunks.append(chunk)
size += len(chunk)
require(size <= maximum, 'file_bound')
after, named = os.fstat(fd), os.lstat(path)
identity = lambda value: (value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns, value.st_mode, value.st_nlink)
require(size == before.st_size and identity(before) == identity(after) == identity(named), 'file_changed')
return b''.join(chunks), (before.st_dev, before.st_ino)
finally:
os.close(fd)
def checked_parent_descriptor(pid):
path = f'/proc/{pid}/fd/10'
executable = f'/proc/{pid}/exe'
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK | os.O_CLOEXEC)
identity = lambda value: (value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns, value.st_mode, value.st_nlink)
try:
before = os.fstat(fd)
require(stat.S_ISREG(before.st_mode) and before.st_nlink in (0, 1) and 0 < before.st_size <= 256 * 1024 * 1024, 'parent_descriptor_shape')
require(identity(before) == identity(os.stat(path)) == identity(os.stat(executable)), 'parent_node_descriptor')
seals = None
if before.st_nlink == 0:
require(all(hasattr(fcntl, name) for name in ('F_GET_SEALS', 'F_SEAL_WRITE', 'F_SEAL_GROW', 'F_SEAL_SHRINK', 'F_SEAL_SEAL')), 'parent_memfd_required')
require(os.readlink(f'/proc/self/fd/{fd}') == '/memfd:paperclip-verified-launch (deleted)' and before.st_mode & 0o222 == 0, 'parent_memfd_required')
required = fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL
seals = fcntl.fcntl(fd, fcntl.F_GET_SEALS)
require(type(seals) is int and seals & required == required, 'parent_memfd_seals')
chunks, size = [], 0
while True:
chunk = os.read(fd, 1024 * 1024)
if not chunk:
break
chunks.append(chunk)
size += len(chunk)
require(size <= 256 * 1024 * 1024, 'parent_descriptor_shape')
require(size == before.st_size and identity(before) == identity(os.fstat(fd)) == identity(os.stat(path)) == identity(os.stat(executable)), 'parent_descriptor_changed')
if seals is not None:
require(fcntl.fcntl(fd, fcntl.F_GET_SEALS) == seals, 'parent_memfd_seals')
return b''.join(chunks), (before.st_dev, before.st_ino)
finally:
os.close(fd)
def checked_runner(path):
before = os.lstat(path)
require(stat.S_ISREG(before.st_mode) or stat.S_ISLNK(before.st_mode), 'runner_shape')
resolved = os.path.realpath(path)
content, inode = checked_file(resolved, 256 * 1024 * 1024)
after = os.lstat(path)
identity = lambda value: (value.st_dev, value.st_ino, value.st_mode, value.st_mtime_ns, value.st_ctime_ns)
require(identity(before) == identity(after) and os.path.realpath(path) == resolved, 'runner_changed')
return content, inode
def parse_closure(content, expected):
manifest = json.loads(content)
entries = manifest.get('entries') if isinstance(manifest, dict) else None
require(isinstance(entries, list) and 0 < len(entries) <= 30000, 'closure_inventory')
normalized, previous, total = [], '', 0
for entry in entries:
require(isinstance(entry, dict) and set(entry) == {'path', 'sha256', 'size', 'executable'}, 'closure_shape')
path = entry['path']
require(isinstance(path, str) and 0 < len(path) <= 4096 and not os.path.isabs(path)
and not re.search(r'[\x00-\x1f\x7f\\]', path) and path > previous
and all(part not in ('', '.', '..') for part in path.split('/'))
and path != 'manifest.json' and not path.startswith('.paperclip-'), 'closure_path')
require(isinstance(entry['sha256'], str) and re.fullmatch(r'[a-f0-9]{64}', entry['sha256'])
and type(entry['size']) is int and 0 <= entry['size'] <= 384 * 1024 * 1024
and type(entry['executable']) is bool, 'closure_metadata')
total += entry['size']
require(total <= 1024 * 1024 * 1024, 'closure_bound')
previous = path
normalized.append({key: entry[key] for key in ('path', 'sha256', 'size', 'executable')})
require(re.fullmatch(r'[a-f0-9]{64}', expected) and digest(json.dumps(normalized, separators=(',', ':'), ensure_ascii=False).encode()) == expected, 'closure_pin')
return normalized
def expected_sources(config, distribution, entries):
bootstrap, guard = config['bootstrapTemplate'], config['guardTemplate']
require(isinstance(bootstrap, str) and 0 < len(bootstrap) <= 32768 and PLACEHOLDER in bootstrap, 'bootstrap_template')
require(isinstance(guard, str) and 0 < len(guard) <= 32768 and guard.count('const digests={};') == 1, 'guard_template')
digests = {distribution + '/' + entry['path']: entry['sha256'] for entry in entries}
return bootstrap.replace(PLACEHOLDER, distribution).encode(), guard.replace('const digests={};', 'const digests=' + json.dumps(digests, separators=(',', ':'), ensure_ascii=False) + ';').encode()
def inspect(config):
require(set(config) == {'root', 'binding', 'runtimeEnvironmentLeaseId', 'runnerdSha256', 'closureSha256', 'nodeSha256', 'guardLoaderSha256', 'bootstrapTemplate', 'guardTemplate'}, 'config_keys')
require(all(isinstance(config[key], str) and re.fullmatch(r'sha256:[a-f0-9]{64}', config[key]) for key in ('runnerdSha256', 'nodeSha256', 'guardLoaderSha256')), 'digest_shape')
binding, expected = config['binding'], config['root']
boot = Path('/proc/sys/kernel/random/boot_id').read_text().strip()
require(re.fullmatch(r'[a-f0-9-]{36}', boot) and boot == expected['bootId'], 'boot_changed')
root = proc(expected['pid'], boot)
require(root == expected and os.getpgid(root['pid']) == root['pid'], 'root_changed')
runtime = binding['remoteCwd'] + '/.paperclip-runtime/paperclip-runner'
runner = runtime + '/bin/paperclip-runnerd'
root_args = argv(root['pid'])
require(root_args[0] == runner and flag(root_args, '--run-id') == binding['runId']
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) + r'/sessions/[a-f0-9]{64}/runner', flag(root_args, '--state-dir')), 'root_session')
runner_bytes, runner_inode = checked_runner(runner)
root_exe = os.stat(f'/proc/{root["pid"]}/exe')
require((root_exe.st_dev, root_exe.st_ino) == runner_inode and 'sha256:' + digest(runner_bytes) == config['runnerdSha256'], 'runner_executable')
metadata, _ = checked_file(PACK + '/provider-assets/kimi/linux-x64/native-closure.json', 4 * 1024 * 1024)
ordered_entries = parse_closure(metadata, config['closureSha256'])
entries = {entry['path']: entry for entry in ordered_entries}
require(NODE in entries and ENTRY in entries and entries[NODE]['executable'] and entries[NODE]['size'] > 0
and entries[ENTRY]['size'] > 0 and 'sha256:' + entries[NODE]['sha256'] == config['nodeSha256'], 'required_entries')
pids = [int(pid) for pid in os.listdir('/proc') if pid.isdigit() and int(pid) > 1]
require(len(pids) <= 4096, 'process_bound')
table = {}
for pid in pids:
try:
table[pid] = proc(pid, boot)
except (FileNotFoundError, ProcessLookupError):
continue
except RuntimeError as error:
if str(error).endswith(':dead_or_invalid_process'):
continue
raise
def ancestry(pid):
chain = []
while pid != root['pid']:
require(pid in table and len(chain) < 64 and pid not in [item['pid'] for item in chain], 'ancestry')
chain.append(table[pid])
pid = table[pid]['ppid']
result = chain + [root]
require(all(int(child['startTicks']) >= int(parent['startTicks']) for child, parent in zip(result, result[1:])), 'ancestry_birth')
return result
matches = []
for pid in table:
try:
chain = ancestry(pid)
except RuntimeError:
continue
if len(chain) < 3:
continue
parent = table[pid]['ppid']
try:
args = argv(parent)
except (FileNotFoundError, ProcessLookupError):
continue
if len(args) != 10 or args[1] != '--eval' or args[4] != BOOTSTRAP:
continue
distribution = args[3]
require(re.fullmatch(r'/tmp/paperclip-acpx-native-[A-Za-z0-9_-]+/distribution', distribution), 'snapshot_path')
require(args[4:] == [BOOTSTRAP, '0', '0', 'commonjs', '[]', '0'] and 'sha256:' + digest(args[2].encode()) == config['guardLoaderSha256'], 'parent_bootstrap')
require(argv(pid) == ['kimi-code'], 'native_title')
for directory in (distribution, os.path.dirname(distribution)):
status = os.lstat(directory)
require(stat.S_ISDIR(status.st_mode) and not status.st_mode & 0o222 and os.path.realpath(directory) == directory, 'snapshot_seal')
require(os.readlink(f'/proc/{pid}/cwd') == binding['remoteCwd'] and os.readlink(f'/proc/{parent}/cwd') == binding['remoteCwd'], 'cwd')
identities = {}
for name in (NODE, ENTRY):
content, inode = checked_file(distribution + '/' + name, 256 * 1024 * 1024, True)
require(len(content) == entries[name]['size'] and digest(content) == entries[name]['sha256'], 'snapshot_digest')
identities[name] = inode
bootstrap, guard = expected_sources(config, distribution, ordered_entries)
require(checked_file(distribution + '/' + BOOTSTRAP, 32768, True)[0] == bootstrap, 'native_bootstrap_bytes')
require(checked_file(distribution + '/' + GUARD, 4 * 1024 * 1024, True)[0] == guard, 'native_guard_bytes')
native_exe, held_node = os.stat(f'/proc/{pid}/exe'), os.stat(f'/proc/{pid}/fd/7')
require((native_exe.st_dev, native_exe.st_ino) == identities[NODE] == (held_node.st_dev, held_node.st_ino), 'native_executable')
if args[0] == '/proc/self/fd/10':
parent_bytes, parent_inode = checked_parent_descriptor(parent)
else:
parent_exe_path = os.path.realpath(f'/proc/{parent}/exe')
parent_bytes, parent_inode = checked_file(parent_exe_path, 256 * 1024 * 1024)
require('sha256:' + digest(parent_bytes) == config['nodeSha256'], 'parent_node')
if args[0] != '/proc/self/fd/10':
require(args[0] == PACK + '/node_modules/node/bin/node' and checked_file(args[0], 256 * 1024 * 1024)[1] == parent_inode, 'parent_node_path')
directory_fd = os.stat(f'/proc/{parent}/fd/4')
directory = os.lstat(distribution)
require(stat.S_ISDIR(directory_fd.st_mode) and (directory_fd.st_dev, directory_fd.st_ino) == (directory.st_dev, directory.st_ino), 'parent_directory_descriptor')
source_mode = os.stat(f'/proc/{parent}/fd/3').st_mode
require(stat.S_ISFIFO(source_mode) or stat.S_ISSOCK(source_mode), 'parent_source_pipe')
for identity in chain:
require(proc(identity['pid'], boot) == identity, 'ancestry_changed')
matches.append({'target': table[pid], 'ancestry': chain, 'nodeSha256': config['nodeSha256'],
'entrypointSha256': 'sha256:' + entries[ENTRY]['sha256'], 'closureSha256': config['closureSha256'],
'guardLoaderSha256': config['guardLoaderSha256'], 'guardSha256': 'sha256:' + digest(guard), 'bootstrapSha256': 'sha256:' + digest(bootstrap),
'entrypointAttribution': 'pinned_native_bootstrap_parent', 'originalChildArgvAvailable': False, 'observedChildTitle': 'kimi-code'})
require(len(matches) == 1, 'unique_native_child')
return matches[0]
def terminate(config):
require(sys.platform == 'linux' and hasattr(os, 'pidfd_open') and hasattr(signal, 'pidfd_send_signal'), 'pidfd_required')
before = inspect(config)
pid = before['target']['pid']
require(pid not in (os.getpid(), config['root']['pid']), 'not_worker')
fd = os.pidfd_open(pid)
try:
require(inspect(config) == before, 'identity_changed_after_pidfd')
fields = dict(line.split(':', 1) for line in Path(f'/proc/self/fdinfo/{fd}').read_text().splitlines() if ':' in line)
require(int(fields.get('Pid', '-1').strip()) == pid, 'pidfd_retired')
signal.pidfd_send_signal(fd, signal.SIGKILL, None, 0)
return {'schema': 'paperclip.e2e.kimi-provider-death.v1', 'binding': config['binding'], 'runtimeEnvironmentLeaseId': config['runtimeEnvironmentLeaseId'],
**before, 'signalled': True, 'signal': 'SIGKILL', 'targetKind': 'kimi_native_child', 'workerSignalled': False}
finally:
os.close(fd)
if __name__ == '__main__':
try:
require(len(sys.argv) == 2 and len(sys.argv[1]) <= 131072, 'input_bound')
print(json.dumps(terminate(json.loads(base64.b64decode(sys.argv[1], validate=True))), separators=(',', ':')))
except Exception:
print(json.dumps({'ok': False, 'code': 'kimi_provider_identity_or_signal_failed'}))
sys.exit(1)