Pinned pi SDK 0.85.1 bridge, Python supervisor, artifact exporter, scripted backend peer, image checks and boundary checks under agent/**. Review findings F1-F3 are recorded in docs/implementation/PI_AGENT_REVIEW.md.
79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
# Executed only by the fake model inside the target image; never on the host.
|
|
import ctypes
|
|
import os
|
|
import signal
|
|
import time
|
|
from pathlib import Path
|
|
assert os.getuid() == os.getgid() == 10001
|
|
assert Path.cwd() == Path('/work')
|
|
assert list(Path('/home/agent').iterdir()) == []
|
|
assert {p.name for p in Path('/work').iterdir()} == {'scratch.md', 'artifacts'}
|
|
assert set(os.environ) <= {'PATH', 'HOME', 'LANG', 'LC_CTYPE', 'PWD', 'SHLVL', '_'}
|
|
assert not Path('/home/vptyp').exists()
|
|
assert not Path('/opt/agent/.env').exists()
|
|
assert not Path('/opt/agent/node_modules/.env').exists()
|
|
assert Path('/sys/fs/cgroup/memory.max').read_text().strip() == '1073741824'
|
|
assert Path('/sys/fs/cgroup/pids.max').read_text().strip() == '128'
|
|
assert Path('/sys/fs/cgroup/cpu.max').read_text().split() == ['100000', '100000']
|
|
for line in Path('/proc/self/status').read_text().splitlines():
|
|
if line.startswith('CapEff:'):
|
|
assert int(line.split()[1], 16) == 0
|
|
if line.startswith('NoNewPrivs:'):
|
|
assert line.split()[1] == '1'
|
|
if line.startswith('Seccomp:'):
|
|
assert line.split()[1] == '2'
|
|
for name in ['/opt/agent/supervisor', '/opt/agent/bridge.ts', '/etc/probe-canary']:
|
|
try:
|
|
with open(name, 'w') as f:
|
|
f.write('tamper')
|
|
except OSError:
|
|
pass
|
|
else:
|
|
raise AssertionError('Root filesystem is writable')
|
|
for name in ['/proc/1/mem', '/proc/1/environ', '/proc/1/fd']:
|
|
try:
|
|
if name.endswith('/fd'):
|
|
os.listdir(name)
|
|
else:
|
|
open(name, 'rb').close()
|
|
except PermissionError:
|
|
pass
|
|
else:
|
|
raise AssertionError('Supervisor proc access is permitted')
|
|
assert ctypes.CDLL(None).ptrace(16, 1, 0, 0) == -1 # PTRACE_ATTACH
|
|
for sig in [signal.SIGSTOP, signal.SIGKILL, signal.SIGTERM, signal.SIGUSR1]:
|
|
os.kill(1, sig)
|
|
time.sleep(0.1)
|
|
assert Path('/proc/1/stat').read_text().rsplit(')', 1)[1].split()[0] not in ['T', 't', 'Z']
|
|
# Actually exhaust the PID cgroup with sleepers; reap them before continuing.
|
|
children = []
|
|
try:
|
|
for i in range(140):
|
|
try:
|
|
pid = os.fork()
|
|
except BlockingIOError:
|
|
break
|
|
if pid == 0:
|
|
time.sleep(10)
|
|
os._exit(0)
|
|
children.append(pid)
|
|
assert 1 < len(children) < 128, 'PID limit was not enforced'
|
|
finally:
|
|
for pid in children:
|
|
os.kill(pid, signal.SIGKILL)
|
|
for pid in children:
|
|
os.waitpid(pid, 0)
|
|
# Exercise memory enforcement rather than only reading the configured limit.
|
|
before = Path('/sys/fs/cgroup/memory.events').read_text()
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
allocation = bytearray(1200 * 1024 * 1024)
|
|
os._exit(0)
|
|
_, status = os.waitpid(pid, 0)
|
|
assert os.WIFSIGNALED(status) and os.WTERMSIG(status) == signal.SIGKILL
|
|
after = Path('/sys/fs/cgroup/memory.events').read_text()
|
|
count = lambda text: int(dict(line.split() for line in text.splitlines())['oom_kill'])
|
|
assert count(after) > count(before), 'Memory limit did not produce a cgroup OOM kill'
|
|
Path('/home/agent/canary').write_text('must disappear on the next run')
|
|
print('isolation checks passed')
|