3"""Offline adversarial tests for remote-GDB arguments, state, and broker."""
5from __future__
import annotations
18from collections.abc
import Callable
19from pathlib
import Path
20from types
import ModuleType, SimpleNamespace
21from unittest
import mock
29 "user@001.002.003.004",
38 "debug@[2001:db8::20]",
65def _load(path: Path, name: str) -> ModuleType:
66 """Execute the exact selected source bytes in a fresh module namespace."""
67 spec = importlib.util.spec_from_file_location(name, path)
68 if spec
is None or spec.loader
is None:
69 msg = f
"cannot load {path}"
70 raise RuntimeError(msg)
71 module = importlib.util.module_from_spec(spec)
72 sys.modules[name] = module
73 spec.loader.exec_module(module)
77def _api(module: ModuleType, name: str) -> object:
78 """Select one exact internal boundary under adversarial test."""
79 return vars(module)[name]
82def _spawn_status(argv: list[str], environment: dict[str, str]) -> int:
83 """Run one fixed offline shell boundary without subprocess search semantics."""
85 (os.POSIX_SPAWN_OPEN, 1,
"/dev/null", os.O_WRONLY, 0o600),
86 (os.POSIX_SPAWN_OPEN, 2,
"/dev/null", os.O_WRONLY, 0o600),
88 pid = os.posix_spawn(argv[0], argv, environment, file_actions=actions)
89 _waited, status = os.waitpid(pid, 0)
90 return os.waitstatus_to_exitcode(status)
93def _case(failures: list[str], name: str, action: Callable[[], object], *, must_fail: bool) ->
None:
94 """Run one both-direction case and record an unexpected result."""
97 except Exception
as exc:
99 failures.append(f
"{name}: unexpected {type(exc).__name__}: {exc}")
102 failures.append(f
"{name}: unexpectedly passed")
105def _host_cases(args: ModuleType, failures: list[str]) ->
None:
106 """Attack SSH host option, metacharacter, and control-byte forms."""
107 for host
in (
"pi.local",
"runner@192.168.1.20",
"192.168.1.20"):
110 f
"valid host {host}",
111 lambda value=host: args.validate_host(value),
136 "debug@[2001:db8::20]",
138 for value
in invalid_hosts:
141 f
"invalid host {value!r}",
142 lambda value=value: args.validate_host(value),
147def _transport_cases(args: ModuleType, failures: list[str]) ->
None:
148 """Attack serial, device, and port boundary forms."""
169 for value
in hostile:
172 f
"invalid serial {value!r}",
173 lambda value=value: args.validate_serial(value),
178 f
"invalid device {value!r}",
179 lambda value=value: args.validate_device(value),
182 _case(failures,
"valid serial",
lambda: args.validate_serial(
"123456789"), must_fail=
False)
185 "valid SEGGER device",
186 lambda: args.validate_device(
"R7KA8D2KF_CPU0"),
189 for port
in (
"1024",
"2331",
"65535"):
192 f
"valid port {port}",
193 lambda value=port: args.validate_port(value),
196 for port
in (
"",
"0",
"1023",
"65536",
"-1",
"+2331",
"23 31"):
199 f
"invalid port {port!r}",
200 lambda value=port: args.validate_port(value),
205def _remote_parse(command: str) -> tuple[int, tuple[str, ...]]:
206 """Drive a generated command through the OpenSSH remote-shell parse layer."""
207 input_read, input_write = os.pipe()
208 output_read, output_write = os.pipe()
210 (os.POSIX_SPAWN_DUP2, input_read, 0),
211 (os.POSIX_SPAWN_DUP2, output_write, 1),
212 (os.POSIX_SPAWN_CLOSE, input_write),
213 (os.POSIX_SPAWN_CLOSE, output_read),
215 pid = os.posix_spawn(
"/bin/sh", [
"/bin/sh",
"-c", command], os.environ, file_actions=actions)
217 os.close(output_write)
220 b
'import os,sys\nos.write(1,b"\\0".join(v.encode("ascii") for v in sys.argv[1:]))\n',
222 os.close(input_write)
224 while chunk := os.read(output_read, 4096):
226 os.close(output_read)
227 _waited, status = os.waitpid(pid, 0)
228 fields = tuple(field.decode(
"ascii")
for field
in bytes(raw).split(b
"\0")
if field)
229 return os.waitstatus_to_exitcode(status), fields
232def _serialization_cases(args: ModuleType, failures: list[str]) ->
None:
233 """Prove exact remote argv after the unavoidable shell serialization."""
239 (
"--",
"R7KA8D2KF_CPU0",
"123456789",
"2331"),
245 (
"--",
"Cortex-M85.rev_1",
"0000123",
"65535"),
248 for device, serial, port, expected
in cases:
249 result, observed = _remote_parse(args.remote_command(serial, port, device=device))
250 if result != 0
or observed != expected:
251 failures.append(f
"remote serialization: {result}, {observed!r}")
254def _remote_cli_cases(args: ModuleType, failures: list[str]) ->
None:
255 """Require all three fields on the sole remote-supervisor command."""
256 parser = _api(args,
"_parser")()
269 [
"remote-command",
"--serial",
"123456789",
"--port",
"2331"],
270 [
"remote-command",
"cleanup",
"--serial",
"123456789",
"--port",
"2331"],
273 with contextlib.redirect_stderr(io.StringIO()):
275 parser.parse_args(argv)
276 except SystemExit
as exc:
277 failures.append(f
"valid remote CLI rejected {argv!r}: {exc.code}")
279 with contextlib.redirect_stderr(io.StringIO()):
281 parser.parse_args(argv)
284 failures.append(f
"invalid remote CLI accepted {argv!r}")
287def _accepts(action: Callable[[], object]) -> bool:
295def _rig_contract_parity(args: ModuleType, root: Path, failures: list[str]) ->
None:
296 """Bind defensive Python parsing to the sole public rig contract corpus."""
297 contract = root /
"scripts/hil/lib/rig_contract.sh"
299 "BASH_ENV":
"/nonexistent",
300 "ENV":
"/nonexistent",
301 "HOME":
"/nonexistent",
303 "PATH":
"/usr/bin:/bin",
305 if _spawn_status([
"/bin/bash",
"-p", str(contract),
"--selftest"], environment) != 0:
306 failures.append(
"public rig contract selftest failed")
308 "PI_HOST": args.validate_host,
309 "JLINK_SN": args.validate_serial,
310 "JLINK_DEVICE": args.validate_device,
312 for field, values
in RIG_PARITY_CORPUS.items():
316 [
"/bin/bash",
"-p", str(contract),
"--validate", field, value],
321 defensive = _accepts(
lambda value=value, field=field: validators[field](value))
322 if public != defensive:
324 f
"rig contract parity mismatch {field}={value!r}: "
325 f
"public={public} defensive={defensive}"
329def _app_cases(args: ModuleType, root: Path, failures: list[str]) ->
None:
330 """Prove option termination and exact single-result app authority."""
331 observed: list[list[str]] = []
333 def valid(argv: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
334 observed.append(argv)
335 return subprocess.CompletedProcess(argv, 0,
"ek_ra8d2::hw_validated::hil::blinky\n",
"")
337 result = args.canonical_app(root,
"--help", valid)
338 if result !=
"ek_ra8d2::hw_validated::hil::blinky" or observed[0][-2:] != [
342 failures.append(
"canonical app did not bind option terminator")
344 def multiline(argv: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
345 return subprocess.CompletedProcess(argv, 0,
"one\ntwo\n",
"")
350 lambda: args.canonical_app(root,
"x", multiline),
355 "live canonical app",
356 lambda: args.canonical_app(root,
"board::stand_alone::ra8d2-ereader"),
359 for selector
in (
"--help",
"bad app",
"bad;app",
"bad\napp",
"../app"):
362 f
"hostile app {selector!r}",
363 lambda value=selector: args.canonical_app(root, value),
368def _workspace(temp: Path) -> tuple[Path, Path, Path]:
369 """Create isolated canonical-looking script and private runtime authorities."""
370 root = (temp /
"workspace").resolve()
371 script = root /
"scripts/dev/remote_gdb_server.sh"
372 script.parent.mkdir(parents=
True)
373 script.write_text(
"#!/bin/bash -p\nexit 0\n", encoding=
"ascii")
375 runtime = temp /
"runtime"
376 runtime.mkdir(mode=0o700)
377 return root, script, runtime
380def _request(guard: ModuleType, temp: Path, parent: int = 4242) -> tuple[object, Path]:
381 """Construct one macOS-shaped request with a private injected runtime."""
382 root, script, runtime = _workspace(temp)
383 request = guard.BrokerRequest(
384 root, script,
"2331",
"", parent, runtime_base=runtime, platform=
"darwin"
386 return request, _api(guard,
"_state_dir")(request)
390 guard: ModuleType, request: object, parent_ref: list[int], signals: list[int]
391) -> tuple[threading.Thread, list[Exception], object]:
392 """Start the production broker in a thread with non-signalling hooks."""
393 errors: list[Exception] = []
394 hooks = guard.BrokerHooks(
395 getppid=
lambda: parent_ref[0],
396 signal_parent=signals.append,
397 pid_alive=
lambda pid: pid == os.getpid(),
400 def target() -> None:
402 _api(guard,
"_run_broker")(request, hooks)
403 except Exception
as exc:
404 errors.append(RuntimeError(f
"{exc}\n{traceback.format_exc()}"))
406 thread = threading.Thread(target=target, daemon=
True)
409 _api(guard,
"_await_broker")(request, os.getpid(), WAIT_SECONDS)
410 except Exception
as exc:
412 _api(guard,
"_request_broker")(
413 _api(guard,
"_state_dir")(request),
"status", request=request
415 detail =
"status unexpectedly passed"
416 except Exception
as status_exc:
417 detail = f
"{type(status_exc).__name__}: {status_exc}"
418 message = f
"broker did not start; thread errors: {errors!r}; status={detail}"
419 raise RuntimeError(message)
from exc
420 return thread, errors, hooks
423def _broker_cases(guard: ModuleType, failures: list[str]) -> dict[str, object]:
424 """Exercise stop, release, parent death, live exclusion, and cleanup."""
425 retained: dict[str, object] = {}
426 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-broker-", dir=
"/tmp")
as directory:
427 request, state_dir = _request(guard, Path(directory))
428 parent_ref = [request.parent_pid]
429 signals: list[int] = []
430 thread, errors, hooks = _start_broker(guard, request, parent_ref, signals)
431 retained.update(_api(guard,
"_read_record")(state_dir).value)
434 "second live broker",
435 lambda: _api(guard,
"_run_broker")(request, hooks),
438 _api(guard,
"_control_broker")(request,
"stop")
439 thread.join(WAIT_SECONDS)
440 if thread.is_alive()
or errors
or signals != [request.parent_pid]:
442 f
"broker stop failed: alive={thread.is_alive()} "
443 f
"errors={errors!r} signals={signals!r}"
445 if (state_dir / guard.STATE_NAME).exists()
or (state_dir / guard.SOCKET_NAME).exists():
446 failures.append(
"broker stop left state or socket")
448 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-parent-death-", dir=
"/tmp")
as directory:
449 request, state_dir = _request(guard, Path(directory))
450 parent_ref = [request.parent_pid]
452 thread, errors, _hooks = _start_broker(guard, request, parent_ref, signals)
454 thread.join(WAIT_SECONDS)
455 _api(guard,
"_control_broker")(request,
"stop")
456 if thread.is_alive()
or errors
or signals:
458 f
"parent death retained broker: alive={thread.is_alive()} "
459 f
"errors={errors!r} signals={signals!r}"
461 if (state_dir / guard.STATE_NAME).exists()
or (state_dir / guard.SOCKET_NAME).exists():
462 failures.append(
"parent death did not atomically clean state")
464 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-release-", dir=
"/tmp")
as directory:
465 request, _state_dir = _request(guard, Path(directory))
466 parent_ref = [request.parent_pid]
468 thread, errors, _hooks = _start_broker(guard, request, parent_ref, signals)
469 _api(guard,
"_control_broker")(request,
"release")
470 thread.join(WAIT_SECONDS)
471 if thread.is_alive()
or errors
or signals:
472 failures.append(
"release did not stop broker without signalling")
476def _protocol_cases(guard: ModuleType, failures: list[str]) ->
None:
477 """Reject malformed protocol, wrong nonce, trailing data, and absent credentials."""
479 good = _api(guard,
"_request_bytes")(
"status", nonce)
480 if _api(guard,
"_parse_request")(good, nonce) !=
"status":
481 failures.append(
"valid protocol request did not round trip")
484 good.replace(b
'"version":1', b
'"version":1,"version":1'),
485 _api(guard,
"_request_bytes")(
"status",
"b" * 64),
486 b
"x" * (guard.MAX_REQUEST_BYTES + 1),
488 for index, raw
in enumerate(attacks):
491 f
"protocol attack {index}",
492 lambda raw=raw: _api(guard,
"_parse_request")(raw, nonce),
497 def getpeereid(self) -> tuple[int, int]:
498 return os.getuid(), os.getgid()
500 if _api(guard,
"_peer_uid")(Peer()) != os.getuid():
501 failures.append(
"getpeereid peer UID was not accepted")
503 name: getattr(socket, name)
504 for name
in (
"SO_PEERCRED",
"LOCAL_PEERCRED")
505 if hasattr(socket, name)
507 for name
in credentials:
508 delattr(socket, name)
512 "missing peer credential primitive",
513 lambda: _api(guard,
"_peer_uid")(object()),
517 for name, credential
in credentials.items():
518 setattr(socket, name, credential)
522 guard: ModuleType, temp: Path, value: dict[str, object]
523) -> tuple[object, Path, dict[str, object]]:
524 """Copy a valid record into a new canonical namespace."""
525 request, state_dir = _request(guard, temp)
526 updated = dict(value)
527 root_stat = request.root.stat()
528 script_stat, script_digest = _api(guard,
"_regular_identity")(request.script)
529 helper_digest = _api(guard,
"_helper_digest")()
532 root=str(request.root),
533 root_dev=root_stat.st_dev,
534 root_ino=root_stat.st_ino,
535 script=str(request.script),
536 script_dev=script_stat.st_dev,
537 script_ino=script_stat.st_ino,
538 script_sha256=script_digest,
539 helper_sha256=helper_digest,
542 return request, state_dir, updated
545def _stale_cases(guard: ModuleType, value: dict[str, object], failures: list[str]) ->
None:
546 """Clean only identity-matching dead state and preserve ambiguity."""
547 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-stale-", dir=
"/tmp")
as directory:
548 request, state_dir, updated = _record_fixture(guard, Path(directory), value)
549 old = _api(guard,
"_publish_record")(state_dir, updated)
550 hooks = guard.BrokerHooks(getppid=
lambda: request.parent_pid, pid_alive=
lambda _pid:
False)
551 _api(guard,
"_prepare_state")(request, hooks)
552 if (state_dir / guard.STATE_NAME).exists():
553 failures.append(
"identity-matching stale state was not cleaned")
554 current = _api(guard,
"_publish_record")(state_dir, updated)
555 _api(guard,
"_unlink_exact")(state_dir, current)
556 replaced = dict(updated)
557 replaced[
"app_arg"] =
"changed"
558 _api(guard,
"_publish_record")(state_dir, replaced)
559 if _api(guard,
"_unlink_exact")(state_dir, old):
560 failures.append(
"stale cleanup removed a replaced record")
562 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-socket-mismatch-", dir=
"/tmp")
as directory:
563 request, state_dir, updated = _record_fixture(guard, Path(directory), value)
564 control = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
565 socket_path = state_dir / guard.SOCKET_NAME
566 control.bind(str(socket_path))
567 socket_path.chmod(0o600)
568 updated[
"socket_ino"] = int(updated[
"socket_ino"]) + 1
569 _api(guard,
"_publish_record")(state_dir, updated)
570 hooks = guard.BrokerHooks(getppid=
lambda: request.parent_pid, pid_alive=
lambda _pid:
False)
573 "ambiguous stale socket",
574 lambda: _api(guard,
"_prepare_state")(request, hooks),
577 if not socket_path.exists()
or not (state_dir / guard.STATE_NAME).exists():
578 failures.append(
"ambiguous stale state was modified")
582def _filesystem_cases(guard: ModuleType, failures: list[str]) ->
None:
583 """Reject links, permissive directories, special records, and duplicate JSON."""
584 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-runtime-", dir=
"/tmp")
as directory:
585 temp = Path(directory)
586 runtime = temp /
"runtime"
587 runtime.mkdir(mode=0o755)
590 "permissive runtime",
591 lambda: _api(guard,
"_runtime_directory")(temp, base=runtime),
595 victim = temp /
"victim"
596 victim.mkdir(mode=0o700)
597 (runtime /
"ra8-remote-gdb").symlink_to(victim, target_is_directory=
True)
600 "linked runtime namespace",
601 lambda: _api(guard,
"_runtime_directory")(temp, base=runtime),
605 for kind
in (
"symlink",
"fifo",
"permissive",
"duplicate"):
606 with tempfile.TemporaryDirectory(prefix=f
"ra8-gdb-{kind}-", dir=
"/tmp")
as directory:
607 _request_value, state_dir = _request(guard, Path(directory))
608 state = state_dir / guard.STATE_NAME
609 if kind ==
"symlink":
610 victim = Path(directory) /
"victim"
611 victim.write_text(
"preserve\n", encoding=
"ascii")
612 state.symlink_to(victim)
614 os.mkfifo(state, 0o600)
615 elif kind ==
"permissive":
616 state.write_text(
"{}\n", encoding=
"ascii")
619 state.write_text(
'{"version":1,"version":1}\n', encoding=
"ascii")
623 f
"unsafe record {kind}",
624 lambda state_dir=state_dir: _api(guard,
"_read_record")(state_dir),
629def _platform_cases(guard: ModuleType, failures: list[str]) ->
None:
630 """Prove the macOS fallback is private and never a shared TMPDIR."""
631 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-home-", dir=
"/tmp")
as directory:
632 home = Path(directory).resolve() /
"home"
633 home.mkdir(mode=0o700)
634 selected = _api(guard,
"_home_runtime")(os.getuid(), home)
635 if home
not in selected.parents
or selected.stat().st_mode & 0o077:
636 failures.append(
"macOS-shaped home fallback is not private")
637 linked = Path(directory) /
"linked-home"
638 linked.symlink_to(home, target_is_directory=
True)
641 "linked home fallback",
642 lambda: _api(guard,
"_home_runtime")(os.getuid(), linked),
645 if "TMPDIR" in str(selected):
646 failures.append(
"runtime fallback used shared TMPDIR")
649def _write_proc(proc: Path, pid: int, root: Path, script: Path, ticks: int) ->
None:
650 """Build a synthetic Linux procfs identity without creating a process."""
651 process = proc / str(pid)
652 (process /
"fd").mkdir(parents=
True, exist_ok=
True)
653 tail = [
"S", *([
"0"] * 18), str(ticks)]
654 (process /
"stat").write_text(f
"{pid} (bash fixture) {' '.join(tail)}\n", encoding=
"ascii")
655 (process /
"status").write_text(
656 f
"Uid:\t{os.getuid()}\t{os.getuid()}\t{os.getuid()}\t{os.getuid()}\n",
659 argv = [
"/bin/bash",
"-p",
"--", str(script),
"run",
"2331"]
660 (process /
"cmdline").write_bytes(b
"\0".join(field.encode(
"ascii")
for field
in argv) + b
"\0")
662 process /
"exe": Path(
"/bin/bash").resolve(),
663 process /
"cwd": root,
664 process /
"fd/255": script,
666 for link, target
in links.items():
667 if not link.exists()
and not link.is_symlink():
668 link.symlink_to(target)
671def _process_cases(guard: ModuleType, failures: list[str]) ->
None:
672 """Attack Linux start-time, argv, and open-script process bindings."""
673 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-proc-", dir=
"/tmp")
as directory:
674 root, script, _runtime = _workspace(Path(directory))
675 proc = Path(directory).resolve() /
"proc"
679 _write_proc(proc, pid, root, script, ticks)
680 request = guard.BrokerRequest(
681 root, script,
"2331",
"", pid, proc_root=proc, platform=
"linux"
685 "valid Linux parent proof",
686 lambda: _api(guard,
"_process_proof")(pid, request),
689 record = SimpleNamespace(value={
"broker_pid": pid,
"broker_start_ticks": ticks})
690 stat_path = proc / str(pid) /
"stat"
691 stat_path.write_text(
692 f
"{pid} (bash fixture) {' '.join(['S', *(['0'] * 18), str(ticks + 1)])}\n",
695 if _api(guard,
"_broker_live")(request, record):
696 failures.append(
"PID start-time reuse passed live identity proof")
697 _write_proc(proc, pid, root, script, ticks)
698 cmdline = proc / str(pid) /
"cmdline"
699 cmdline.write_bytes(cmdline.read_bytes()[:-1] + b
"forged\0")
702 "forged Linux parent argv",
703 lambda: _api(guard,
"_process_proof")(pid, request),
706 _write_proc(proc, pid, root, script, ticks)
707 (proc / str(pid) /
"fd/255").unlink()
710 "missing Linux script descriptor",
711 lambda: _api(guard,
"_process_proof")(pid, request),
716def _schema_cases(guard: ModuleType, value: dict[str, object], failures: list[str]) ->
None:
717 """Reject duplicate, extra, missing, Boolean, and wrong-type record fields."""
719 lambda item: item.update(version=
True),
720 lambda item: item.update(parent_pid=
True),
721 lambda item: item.update(parent_argv=
"not-a-list"),
722 lambda item: item.update(extra=
"field"),
723 lambda item: item.pop(
"nonce"),
725 for index, mutate
in enumerate(mutations):
726 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-schema-", dir=
"/tmp")
as directory:
727 _request_value, state_dir = _request(guard, Path(directory))
728 changed = dict(value)
732 f
"record schema mutation {index}",
733 lambda changed=changed, state_dir=state_dir: _api(guard,
"_publish_record")(
740def _pidfd_order_case(guard: ModuleType, failures: list[str]) ->
None:
741 """Model the old reuse bug, then prove capability acquisition occurs first."""
743 authenticated = generation[0]
745 opened = generation[0]
746 if authenticated == opened:
747 failures.append(
"negative PID-reuse control did not model different identities")
748 events: list[str] = []
749 request = guard.BrokerRequest(Path(
"/x"), Path(
"/x/s"),
"2331",
"", 42, platform=
"linux")
751 def open_capability(_request: object) -> tuple[Callable[[int],
None], int]:
752 events.append(
"open")
753 return lambda _pid:
None, os.open(
"/dev/null", os.O_RDONLY)
755 def reject(_pid: int, _request: object) -> object:
756 events.append(
"authenticate")
757 message =
"modeled PID reuse"
758 raise guard.GuardError(message)
760 def live(_fd: int) -> bool:
764 mock.patch.object(guard,
"_signal_authority", open_capability),
765 mock.patch.object(guard,
"_process_proof", reject),
766 mock.patch.object(guard,
"_pidfd_live", live),
770 "pidfd-bound authentication reuse",
771 lambda: _api(guard,
"_parent_authority")(
772 request, guard.BrokerHooks(getppid=
lambda: 42)
776 if events != [
"open",
"authenticate"]:
777 failures.append(f
"pidfd ordering was not open-before-authenticate: {events!r}")
780def _shell_boundary(root: Path, failures: list[str]) ->
None:
781 """Drive sanitizer and cwd checks through the exact production wrapper."""
782 script = root /
"scripts/dev/remote_gdb_server.sh"
783 with tempfile.TemporaryDirectory(prefix=
"ra8-gdb-bash-func-", dir=
"/tmp")
as directory:
784 marker = Path(directory) /
"imported"
786 "BASH_ENV": str(Path(directory) /
"missing"),
787 "BASH_FUNC_cd%%": f
'() {{ /usr/bin/touch {marker}; builtin cd "$@"; }}',
788 "BASH_FUNC_ra8_remote_gdb_probe%%": f
"() {{ /usr/bin/touch {marker}; }}",
790 "PATH":
"/usr/bin:/bin",
792 control = _spawn_status([
"/bin/bash",
"-c",
"ra8_remote_gdb_probe"], environment)
793 if control != 0
or not marker.exists():
794 failures.append(
"raw exported-function attack control was vacuous")
795 marker.unlink(missing_ok=
True)
796 result = _spawn_status([
"/bin/bash",
"-p", str(script),
"invalid-action"], environment)
797 if result == 0
or marker.exists():
798 failures.append(
"raw BASH_FUNC entry ran at privileged wrapper boundary")
799 previous = os.open(
".", os.O_RDONLY)
802 descendant = _spawn_status(
803 [
"/bin/bash",
"-p", str(script),
"--boundary-selftest"], environment
808 if descendant != 0
or marker.exists():
809 failures.append(
"production wrapper leaked a function to an ordinary Bash descendant")
812def _remote_program_cases(remote_helper: Path, root: Path, failures: list[str]) ->
None:
813 """Run the remote supervisor selftest and reject PID-sweep regressions."""
814 remote = _load(remote_helper,
"ra8_remote_gdb_remote")
815 if remote.selftest() != 0:
816 failures.append(
"remote direct-child supervisor selftest failed")
817 source = (root /
"scripts/dev/remote_gdb_server.sh").read_text(encoding=
"ascii")
819 f
"remote PID-sweep residue remains: {forbidden}"
820 for forbidden
in (
"/proc/[0-9]*",
"REMOTE_CLEANUP",
"server_pids")
821 if forbidden
in source
825def selftest(helper: Path, args_helper: Path, remote_helper: Path, root: Path) -> int:
826 """Run all offline both-direction tests without real signals or network."""
827 guard = _load(helper,
"ra8_remote_gdb_guard")
828 args = _load(args_helper,
"ra8_remote_gdb_args")
829 failures: list[str] = []
830 _host_cases(args, failures)
831 _transport_cases(args, failures)
832 _serialization_cases(args, failures)
833 _remote_cli_cases(args, failures)
834 _rig_contract_parity(args, root, failures)
835 _app_cases(args, root, failures)
836 record = _broker_cases(guard, failures)
837 _protocol_cases(guard, failures)
838 _stale_cases(guard, record, failures)
839 _filesystem_cases(guard, failures)
840 _platform_cases(guard, failures)
841 _process_cases(guard, failures)
842 _schema_cases(guard, record, failures)
843 _pidfd_order_case(guard, failures)
844 _shell_boundary(root, failures)
845 _remote_program_cases(remote_helper, root, failures)
847 for failure
in failures:
848 print(f
" [FAIL] {failure}", file=sys.stderr)
850 print(
"remote_gdb_guard_selftest.py: PASS (transport/state/broker/platform/PID-reuse)")
855 """Parse exact authorities and run the offline suite."""
856 parser = argparse.ArgumentParser(description=__doc__)
857 parser.add_argument(
"--helper", type=Path, required=
True)
858 parser.add_argument(
"--args-helper", type=Path, required=
True)
859 parser.add_argument(
"--remote-helper", type=Path, required=
True)
860 parser.add_argument(
"--root", type=Path, required=
True)
861 options = parser.parse_args()
863 options.helper.resolve(strict=
True),
864 options.args_helper.resolve(strict=
True),
865 options.remote_helper.resolve(strict=
True),
866 options.root.resolve(strict=
True),
870if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.