8.4 KiB
Protected radio ownership (C01 application ABI v7)
The radio owner retains one externally appointed, already-open USB TTY. The application receives a finite packet channel, not the TTY descriptor. Raw listener/probe IO and packaged firmware tools therefore share the same exclusive reservation. No request can choose another device, executable, HOME, shell command, or root argv.
Launch and custody
image-recipe/configs/archipelago-radio-owner.service is an uninstalled
service template. Installing/enabling it requires an external node appointment;
starting the application does not install it or grant hardware permissions.
-
The fixed native entry point is
archipelago radio-owner-v1. -
/etc/archipelago-c01/radio.jsonmust be root-owned, mode 0600, with protected ancestry. Its exact version-1 fields are:Field Binding version1pathOne canonical /dev/ttyUSB<n>or/dev/ttyACM<n>; no aliasidentity[st_dev, st_ino, st_rdev, st_ctime_ns]of that device nodeboardheltecv3orheltecv4, explicitly appointedruntime_uid,runtime_gidMust match the independently qualified C01 runtime homeExplicit retained RNode/Reticulum runtime store; mode 0700, runtime-owned, root-protected ancestry home_identity[st_dev, st_ino]of that retained directorycustodyMap of retained key filenames relative to HOME to SHA-256 digests rnode_sha256,esptool_sha256,daemon_sha256Exact installed frozen helper bytes Custody includes both
.config/rnodeconf/firmware/signing.keyand.config/rnodeconf/firmware/device.key, plus the complete set of.config/rnodeconf/trusted_keys/*.pubkeyfiles. Missing identities are an appointment failure, not permission to generate replacement keys.HOME must be the already-qualified curated runtime HOME or a dedicated retained radio store containing only
.config/rnodeconfand.archy-reticulum. Overlap with any C01 private backing is refused. An operator's complete HOME must not be copied or granted to satisfy this appointment. -
Deployment supplies an exact
DeviceAllowdrop-in and an exact writable/bound retained HOME. It must not grant dialout membership, expose all of/dev, or relax the application/runtime execution guard. For the authorized Framework test the device is/dev/ttyUSB0, Heltec V3. Yaya's LTE modem is unrelated. -
The owner checks the node identity after open, establishes
TIOCEXCL, and rejects pre-existing openers. It never reopens the TTY. Unplug, replacement, re-enumeration, or metadata change retires the appointment. The current inode appointment is lifetime-specific: a changed device node, including after a reboot, requires a new external appointment rather than automatic guessing. -
Helpers join the existing live runtime worker's pinned mount/network views and exclusive user hierarchy, then permanently drop to its UID/GID, with no supplementary groups, no capabilities and no-new-privileges. The existing C01 access/exclusion policy remains authoritative. Runtime lease expiry prevents new execution and retires active work.
ABI and wire contract
Nodes without the root-owned radio endpoint continue using application ABI v6.
When the endpoint exists, the supervisor selects c01-managed-v7, supplies
FD14, and binds radio_owner_pid in the authenticated launch grant. FD12
(Tor) and the authority's FD13 (epoch key) keep their existing meanings. A v7
launch without FD14 refuses before setup can reuse the missing descriptor slot.
The root supervisor binds the actual application PID/UID/GID before releasing the native application. Every application packet uses kernel SCM credentials, an increasing sequence, version 1, and an absolute BOOTTIME deadline bounded to two seconds. The owner also retains the application's pidfd. Forwarding a channel to another process does not transfer caller authority. The configured manager UID/GID, permanently dropped credentials, empty capability sets and no-new-privileges are checked at binding. Client deadlines begin before blocking-work admission; cancelled or expired queued IO cannot write later.
Inventory returns an unpredictable appointment token. Acquiring it returns a new owner-created packet socket. One such session may exist at a time. IO is bounded to 4096 bytes per operation; sessions have a 30-second idle deadline. Helpers execute only fixed actions. Firmware images are size-bounded, checked against their declared digest and sealed before execution. There is no manager file-path or executable-FD input.
The helper ABI is separate from the application ABI:
| Helper FD | Meaning |
|---|---|
| 3 | Same retained open TTY description; pyserial duplicates it, never opens its pathname |
| 4 | Retained runtime HOME directory |
| 5 | Sealed firmware image, or sealed Reticulum daemon configuration |
| 6 | Direct Reticulum daemon RPC stream, when starting the listener |
| 7 | Sealed signing/trust-key snapshot for RNode install/verification |
| 8 | Per-job extraction workspace; TMPDIR uses this FD, not shared /tmp |
The RNode wrapper uses its own embedded esptool entry point. It never executes the downloaded Python flasher. Existing signing/device keys and trusted public keys are read from the sealed snapshot; upstream key creation/overwrite is refused. Other firmware/cache data continues to use the retained HOME.
Only the domain-separated 64-byte RNS identity is passed to the listener. The HKDF labels and salt match the existing Python derivation, preserving the RNS address; the Archy node seed and wallets are not granted to the helper. Saved RF settings are preserved. If the application has no saved RF profile, the owner reads the retained RNS profile; defaults apply only when neither profile exists, not when an existing profile is corrupt or ambiguous.
Retirement
Closing a session, application death, idle expiry, or cancellation starts
retirement. The owner retains the reservation through leader exit and the
kernel's empty-cgroup barrier, including detached/setsid descendants. Verify
and capability helpers have a 30-second ceiling; firmware writers have a
30-minute ceiling. The long-lived listener instead requires its live session
and runtime leases.
The root-only /run/archipelago-radio/job.json records the exact outstanding
cgroup identity before fork. radio-retire-v1, installed as ExecStopPost,
handles abrupt owner death. It refuses a live owner or substituted cgroup and
does not discharge a reservation from pathname absence alone. An unreadable or
unretired reservation prevents replacement. RuntimeDirectory preservation keeps
that obligation across a failed service stop.
PyInstaller extraction uses a per-job directory under the root-private
/var/lib/archipelago-radio-work, passed as FD8. Only that directory is cleaned
after the writer cgroup is empty, using an inode check and descriptor-relative
removal. Persistent HOME, signing keys, RF settings and wallet data are not
cleanup targets. This also avoids dependence on space in a shared /tmp.
Disposable qualification
bash scripts/test-radio-owner-isolated.sh: PTY-only root/application/runtime credential, channel, IO, replay, late admission, caller-death and descendant retirement checks.bash scripts/test-radio-owner-isolated.sh --retirement: abrupt owner death and exact cgroup recovery.bash scripts/test-radio-owner-isolated.sh --handoff: real C01Childrenpre-callback descriptor sweep, credential drop and native FD14 remapping; the authority's FD13 is not leaked into that handoff.ARCHY_TEST_RADIO_OWNER=1 bash scripts/test-backend-isolated.sh: actual Rust client under manager exec denial, using the same finite owner and PTY fixture.bash scripts/test-radio-owner-isolated.sh ARTIFACT_DIRECTORY: exact frozen RNode helper, with its original PTY pathname hidden in the helper's private mount view. Requires the syntheticpty-verification.jsongenerated byreticulum-daemon/prepare_owned_radio_fixture.py; verifies a real KISS/RSA exchange and rejects a corrupted EEPROM signature.bash scripts/test-radio-abi-isolated.sh NATIVE_BINARY: actual non-test native v6/v7 descriptor boundaries and fixed owner/retirement entry points.
PTY modem-control behavior is explicitly a fixture seam. These tests do not establish physical reset timing, firmware-write acceptance, installed-node UAT, or permission to close radio issues without those results.