3"""Exact Linux child-subreaper policy for the image selftest supervisor."""
5from __future__
import annotations
10 "child subreaper capability constant changed":
"PR_SET_CHILD_SUBREAPER = 36",
11 "child subreaper verification constant changed":
"PR_GET_CHILD_SUBREAPER = 37",
12 "child subreaper prctl activation removed": (
13 "if prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0:"
15 "child subreaper prctl verification removed": (
16 "result = prctl(PR_GET_CHILD_SUBREAPER, ctypes.addressof(state), 0, 0, 0)"
18 "child subreaper verified-state proof removed": (
"return result == 0 and state.value == 1"),
19 "child subreaper activation assignment removed": (
20 " self.subreaper = _enable_child_subreaper()"
22 "child subreaper initial-empty proof removed": (
23 "return self.subreaper and _child_table_is_empty()"
25 "manual child subreaper gate removed": (
26 " if not self.subreaper or self.child is not None or self.pid is not None:"
28 "manual child containment binding removed": (
29 " self.child = child\n"
30 " self.pid = child.pid\n"
31 " self.children_contained = False"
33 "manual child isolated identity proof removed": (
34 " and identity.pid == identity.group == identity.session"
36 "manual child direct-parent identity proof removed": (
37 " and children.get(child.pid) == identity"
39 "manual child terminal authority initialization removed": (
40 " self.leader_terminal = False"
42 "manual child WNOWAIT observation removed": (
43 " terminal = os.waitid(\n"
46 " os.WEXITED | os.WNOHANG | os.WNOWAIT,\n"
49 "manual child early-reap PID release removed": (
50 " except ChildProcessError:\n"
51 " self.reaped = True\n"
52 " self.leader_terminal = True"
54 "manual child terminal state binding removed": (
55 " self.leader_terminal = terminal is not None"
57 "manual terminal child signal bypass removed": (
58 " if self.leader_terminal:\n return self._finish_terminal_leader()"
60 "child subreaper leader exclusion removed": (
61 " leader = children.pop(excluded_pid, None)\n"
62 " if leader != self.leader_identity:"
64 "child subreaper adopted exclusion dispatch removed": (
65 " children = self._bound_direct_children(excluded_pid)"
67 "child subreaper leader WNOWAIT retention removed": (
68 " result = os.waitid(\n"
71 " os.WEXITED | os.WNOHANG | os.WNOWAIT,\n"
74 "child subreaper lost authority fail-stop removed": (
75 " if self.authority_lost:\n return False"
77 "child subreaper pre-reap adopted drain removed": (
78 " descendants_drained = leader_terminal and self._cleanup_adopted_children(leader)"
80 "child subreaper direct-parent binding removed": (
81 " if parent != os.getpid() or identity is None:\n return None"
83 "child subreaper pre-signal identity proof removed": (
84 " if _bind_process(authority.pid) != authority:\n"
86 " with suppress(ProcessLookupError):"
88 "child subreaper exact kill removed":
"os.kill(authority.pid, signal.SIGKILL)",
89 "child subreaper WNOWAIT proof removed": (
90 " result = os.waitid(\n"
93 " os.WEXITED | os.WNOHANG | os.WNOWAIT,\n"
96 "child subreaper pre-reap identity proof removed": (
97 " if _bind_process(authority.pid) != authority:\n"
99 " waited, _status = os.waitpid(authority.pid, 0)"
101 "child subreaper exact reap removed":
"return waited == authority.pid",
102 "child subreaper ECHILD completion removed": (
103 " except ChildProcessError:\n return True\n return False"
105 "child subreaper empty completion removed": (
" if _child_table_is_empty():"),
106 "child subreaper enable call removed":
" if not supervisor.enable_subreaper():",
107 "child subreaper blocking containment removed": (
108 " while not cleaned and not self.children_contained:"
110 "child subreaper final containment call removed":
" supervisor.contain()",
113MOVED_PROCESS_TOKENS = {
114 "bound-exit parent-death pipe removed": (
115 "self.death_read, self.death_write = os.pipe2(os.O_CLOEXEC)"
117 "bound-exit parent-death descriptor propagation removed": (
118 "inherited = [source_descriptor, self.death_read, root_descriptor]"
120 "bound-exit controller interpreter changed":
" sys.executable,",
121 "bound-exit controller no-bytecode isolation removed":
' "-B",',
122 "bound-exit controller isolated-mode flag removed":
' "-I",',
123 "bound-exit controller site-import refusal removed":
' "-S",',
124 "bound-exit controller immutable helper path removed": (
125 ' SUPERVISOR_PROGRAM,\n "--controller",'
127 "process stat bytes parser replaced with text decoding":
'(entry / "stat").read_bytes()',
128 "bound-exit group cleanup reduced to leader PID":
"os.killpg(leader, signal.SIGKILL)",
129 "bound-exit cleanup signal block moved after authority checks": (
130 " signal.pthread_sigmask(signal.SIG_BLOCK, MANAGED_SIGNALS)\n"
131 " if self.authority_lost:"
135PROCESS_STATIC_TOKENS = (
136 "root_descriptor = _anchored_root_descriptor(launch.status)",
137 "members is not None and members <= {leader}",
138 " if not self._reap():\n self.cleaning = False\n return False",
141PROCESS_LOADER_TOKENS = {
142 "process source metadata regular binding removed": (
143 "_read_process_source",
144 "stat.S_ISREG(metadata.st_mode)",
146 "process source metadata link binding removed": (
147 "_read_process_source",
148 "metadata.st_nlink == 1",
150 "process source metadata owner binding removed": (
151 "_read_process_source",
152 "metadata.st_uid == os.getuid()",
154 "process source metadata group binding removed": (
155 "_read_process_source",
156 "metadata.st_gid == os.getgid()",
158 "process source metadata mode binding removed": (
159 "_read_process_source",
160 "stat.S_IMODE(metadata.st_mode) == PROCESS_MODE",
162 "process source metadata byte bound removed": (
163 "_read_process_source",
164 "0 < metadata.st_size <= PROCESS_MAX_BYTES",
166 "process source read step bound removed": (
167 "_read_process_source",
168 "for _step in range(PROCESS_READ_STEPS):",
170 "process source descriptor pread binding removed": (
171 "_read_process_source",
172 "chunk = os.pread(descriptor, 4096, offset)",
174 "process source complete-size postcondition removed": (
175 "_read_process_source",
176 "if len(source) != metadata.st_size or len(source) > PROCESS_MAX_BYTES:",
178 "process loader bound source read removed": (
180 "source = _read_process_source(descriptor)",
182 "process loader pre-exec digest binding removed": (
184 "if digest != PROCESS_RAW_SHA256:",
186 "process loader namespace name binding removed": (
188 'module_name = "_ra8_supervisor_process"',
190 "process loader private module construction removed": (
192 "module = types.ModuleType(module_name)",
194 "process loader private module namespace removed": (
196 "namespace = module.__dict__",
198 "process loader namespace filename binding removed": (
200 'namespace["__file__"] = f"/proc/self/fd/{descriptor}"',
202 "process loader grant version binding removed": (
204 'namespace["_RA8_SUPERVISOR_PROCESS_VERSION"] = 1',
206 "process loader authenticated exec removed": (
208 "exec( # noqa: S102 -- exact digest-bound source-only FD",
210 "process loader compile source binding removed": (
"_load_process_api",
"compile(source,"),
211 "process loader compile filename binding removed": (
213 'namespace["__file__"],',
215 "process loader compile exec-mode binding removed": (
"_load_process_api",
', "exec")'),
216 "process loader grant exact consumption removed": (
218 'grant = namespace.pop("_RA8_SUPERVISOR_PROCESS_VERSION", None)',
220 "process loader grant absence postcondition removed": (
222 'if grant != 1 or "_RA8_SUPERVISOR_PROCESS_VERSION" in namespace:',
224 "process loader post-exec same-FD digest removed": (
226 "if hashlib.sha256(_read_process_source(descriptor)).hexdigest() != digest:",
228 "process loader validation delegation removed": (
230 "return _validate_process_api(module_name, namespace, module)",
232 "process loader class API binding removed": (
233 "_validate_process_api",
234 "if not all(isinstance(value, type) for value in classes) or not all(",
236 "process loader function API binding removed": (
237 "_validate_process_api",
238 "isinstance(value, types.FunctionType) for value in functions",
240 "process loader API namespace identity removed": (
241 "_validate_process_api",
244 "process loader class namespace binding removed": (
245 "_validate_process_api",
246 'escaped = any(value.__module__ != namespace["__name__"] for value in classes)',
248 "process loader function namespace binding removed": (
249 "_validate_process_api",
250 "escaped = escaped or any(value.__globals__ is not namespace for value in functions)",
252 "process loader method namespace binding removed": (
253 "_validate_process_api",
254 "escaped = escaped or any(value.__globals__ is not namespace for value in methods)",
256 "process loader preexisting module refusal removed": (
258 "if module_name in sys.modules:",
260 "process loader module registration removed": (
262 "sys.modules[module_name] = module",
264 "process loader module identity postcondition removed": (
265 "_validate_process_api",
266 "escaped = escaped or sys.modules.get(module_name) is not module",
268 "process loader module residue cleanup removed": (
270 "if module is not None and sys.modules.get(module_name) is module:\n"
271 " del sys.modules[module_name]",
273 "process loader descriptor final-close removed": (
275 "os.close(descriptor)",
277 "process loader install residue refusal removed": (
278 "_install_process_api",
279 'if "_ra8_supervisor_process" in sys.modules:',
283PROCESS_LOADER_ORDER = (
284 'module_name = "_ra8_supervisor_process"',
285 "if module_name in sys.modules:",
286 "module = types.ModuleType(module_name)",
287 "namespace = module.__dict__",
288 'namespace["_RA8_SUPERVISOR_PROCESS_VERSION"] = 1',
289 "sys.modules[module_name] = module",
290 "exec( # noqa: S102 -- exact digest-bound source-only FD",
291 'grant = namespace.pop("_RA8_SUPERVISOR_PROCESS_VERSION", None)',
292 "if hashlib.sha256(_read_process_source(descriptor)).hexdigest() != digest:",
293 "return _validate_process_api(module_name, namespace, module)",
294 "if module is not None and sys.modules.get(module_name) is module:",
295 "del sys.modules[module_name]",
296 "os.close(descriptor)",
298PROCESS_VALIDATION_ORDER = (
299 "api = tuple(namespace.get(name) for name in names)",
300 "classes, functions = api[:2], api[2:]",
301 "if not all(isinstance(value, type) for value in classes) or not all(",
303 'escaped = any(value.__module__ != namespace["__name__"] for value in classes)',
304 "escaped = escaped or sys.modules.get(module_name) is not module",
307PROCESS_LOADER_ORDER_DIAGNOSTIC = (
308 "devcontainer image supervisor: authenticated process-loader order drifted"
311SUPERVISOR_SUBREAPER_LABELS = frozenset(
312 {
"child subreaper enable call removed",
"child subreaper final containment call removed"}
314SUPERVISOR_SUBREAPER_OWNERS = {
315 "child subreaper enable call removed":
"_supervise",
316 "child subreaper final containment call removed":
"_supervise",
318SUPERVISOR_SUBREAPER_ORDER = (
319 "if not supervisor.enable_subreaper():",
320 "old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, MANAGED_SIGNALS)",
321 "supervisor.spawn(source_descriptor, launch)",
322 " supervisor.contain()",
324PROCESS_SUBREAPER_ORDER = (
325 "os.killpg(leader, signal.SIGKILL)",
326 "leader_terminal = self._wait_leader_terminal()",
327 "self._cleanup_adopted_children(leader)",
328 "members = _group_members(leader) if descendants_drained else None",
329 "members is not None and members <= {leader} and self._reap()",
330 "descendants_clean = self._cleanup_adopted_children()",
331 "return descendants_clean and self._close_entry_authority()",
335def semantic_findings(label: str) -> tuple[str, ...] |
None:
336 """Return the exact focused finding for one child-subreaper mutation."""
337 loader = PROCESS_LOADER_TOKENS.get(label)
338 if loader
is not None:
339 function, token = loader
341 "devcontainer image supervisor: "
342 f
"{function} process-loader token is not unique: {token}",
344 if label ==
"process loader authentication order changed":
345 return (PROCESS_LOADER_ORDER_DIAGNOSTIC,)
346 token = SUBREAPER_TOKENS.get(label, MOVED_PROCESS_TOKENS.get(label))
350 "devcontainer image supervisor"
351 if label
in SUPERVISOR_SUBREAPER_LABELS
352 else "devcontainer image supervisor process"
354 finding = f
"{authority}: required process-authority token is not unique: {token}"
355 findings = (finding,)
357 "child subreaper enable call removed",
358 "child subreaper final containment call removed",
360 findings += (
"devcontainer image supervisor: subreaper cleanup order drifted",)
361 if label ==
"bound-exit group cleanup reduced to leader PID":
362 findings += (
"devcontainer image supervisor: subreaper cleanup order drifted",)
363 if label ==
"bound-exit cleanup signal block moved after authority checks":
364 authority_token = SUBREAPER_TOKENS[
"child subreaper lost authority fail-stop removed"]
366 "devcontainer image supervisor process: required process-authority token "
367 f
"is not unique: {authority_token}",
369 if label ==
"child subreaper lost authority fail-stop removed":
370 moved = MOVED_PROCESS_TOKENS[
"bound-exit cleanup signal block moved after authority checks"]
372 "devcontainer image supervisor process: required process-authority token "
373 f
"is not unique: {moved}",
375 if label ==
"child subreaper pre-reap adopted drain removed":
376 findings += (
"devcontainer image supervisor: subreaper cleanup order drifted",)
380def _function_source(source: str, name: str) -> str |
None:
381 """Return one complete top-level function from parsed source."""
383 module = ast.parse(source)
386 for node
in module.body:
387 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == name:
388 return ast.get_source_segment(source, node)
392def _loader_errors(supervisor: str) -> list[str]:
393 """Bind every process-loader property inside its exact owning function."""
395 for function, token
in PROCESS_LOADER_TOKENS.values():
396 body = _function_source(supervisor, function)
397 if body
is None or body.count(token) != 1:
399 "devcontainer image supervisor: "
400 f
"{function} process-loader token is not unique: {token}"
403 (
"_load_process_api", PROCESS_LOADER_ORDER),
404 (
"_validate_process_api", PROCESS_VALIDATION_ORDER),
406 for function, order
in order_specs:
407 body = _function_source(supervisor, function)
408 if body
is not None and all(body.count(token) == 1
for token
in order):
409 positions = tuple(body.index(token)
for token
in order)
410 if positions != tuple(sorted(positions)):
411 findings.append(PROCESS_LOADER_ORDER_DIAGNOSTIC)
415def errors(supervisor: str, process_source: str) -> list[str]:
416 """Require every subreaper token once and its cleanup chain in order."""
418 for label, token
in SUBREAPER_TOKENS.items():
419 if label
in SUPERVISOR_SUBREAPER_LABELS:
420 owner = SUPERVISOR_SUBREAPER_OWNERS[label]
421 source = _function_source(supervisor, owner)
or ""
423 source = process_source
425 "devcontainer image supervisor"
426 if label
in SUPERVISOR_SUBREAPER_LABELS
427 else "devcontainer image supervisor process"
429 if source.count(token) != 1:
430 findings.append(f
"{authority}: required process-authority token is not unique: {token}")
432 "devcontainer image supervisor process: required process-authority token is not unique: "
434 for token
in MOVED_PROCESS_TOKENS.values()
435 if process_source.count(token) != 1
438 "devcontainer image supervisor process: required process-authority token is not unique: "
440 for token
in PROCESS_STATIC_TOKENS
441 if process_source.count(token) != 1
444 (supervisor, SUPERVISOR_SUBREAPER_ORDER),
445 (process_source, PROCESS_SUBREAPER_ORDER),
447 for source, order
in orders:
450 position = source.find(token, position + 1)
452 findings.append(
"devcontainer image supervisor: subreaper cleanup order drifted")
454 return findings + _loader_errors(supervisor)