Resource manager authority and device grants
r2kernel-resourced requires a root-provisioned authority policy. The installer creates /etc/r2kernel/resource-authority.json; R2KERNEL_RESOURCE_AUTHORITY_POLICY selects a different root-managed policy. Existing policy files are preserved and must explicitly include every field below before upgrading, except link_layer_grants, which may be absent (no link-layer capability is then approved).
{
"allowed_uids": [0, 950],
"manager_executables": [
"<prefix>/runtime/bin/r2kernel-control",
"<prefix>/runtime/bin/r2kernel-launch",
"<prefix>/runtime/bin/r2kernel-subsystem",
"<prefix>/runtime/bin/r2kernel",
"<prefix>/runtime/bin/r2kernel-resourced"
],
"package_cgroup_root": "/sys/fs/cgroup/r2kernel-packages",
"control_uid": 950,
"control_gid": 950,
"root_launch_uid": 951,
"root_launch_gid": 951,
"federation_key_path": "<prefix>/etc/federation.key",
"device_grants": [],
"link_layer_grants": []
}
UID/GID 950 identify the Control account and 951 identify a separate package account; use the installed accounts' actual IDs. The package UID must differ from the Control UID. The policy, executable files, cgroup scope and their parent directories must be root-owned, without symlinks or group/world write permission. Use canonical executable paths: substitute the canonical absolute install prefix for <prefix> in the example. The federation key is a root-only regular file (mode 0600, 16–4096 bytes); only an authenticated manager can load its material through the broker. Status and allocation snapshots contain no key material.
A manager must match an approved UID and executable inode and remain outside the package cgroup subtree. A manager can launch only packages it can read itself: the resource service reads the package with the manager's user, group and supplementary groups. Every managed child enters a root-owned lease cgroup before its package entrypoint executes. Executing a manager binary from inside a package retains its package scope and cannot confer manager authority. Manager entrypoints disable process dumping; package seccomp denies tracing, cross-process memory and cross-process descriptor (pidfd_getfd) syscalls. Seccomp does not cover /proc/<pid>/mem; the kernel's ptrace rules govern it. With kernel.yama.ptrace_scope 1 or higher (the Ubuntu default), a process can open the memory only of its own descendants, so managed packages cannot reach each other, and a sandboxed package cannot reach a same-account process outside its own tree. With kernel.yama.ptrace_scope=0, any process of the same account, sandboxed or not, can read and write another's memory this way. Keep ptrace_scope at 1 or higher on hosts that run brokered packages beside other code of the same account. Foreground attachment is a single-use transition for the claiming manager's direct child, identified by PID and Linux process start time. Reattachment to a different process is rejected. Lifecycle changes, allocation inspection, shared-resource changes and federation-key reads require manager authority. Package FD requests require the original attached process identity and its resource grants. A brokered CAN descriptor is not confined to its claimed interface: its holder can bind it again or name another interface in each sendmsg, and so reach every CAN interface of the host, including interfaces another package claims exclusively. A package without resources.sandbox.can: brokered can open raw CAN sockets itself, because Linux requires no capability for them; see Resource Control. A brokered UDP descriptor is likewise not confined to its claimed adapter for sending: its holder can name another interface for each datagram (IP_PKTINFO). Manager clients verify that the connected broker is a root peer before trusting any response. The broker socket stays root-owned; shared IPC directories use root ownership and sticky protection.
Linux cgroup v2, root-controlled package scope, openat2, pidfd support and cgroup.kill (Linux 5.14+) are required. Missing authority prerequisites fail explicitly. Seccomp supports native x86-64 or aarch64 ABI; compatibility architectures and x32-tagged calls return EPERM. All detached launches and root/Control foreground launches run package code under the configured separate non-root UID/GID; privileged root package execution is unsupported. Other foreground operators retain their existing non-root execution identity. Broker foreground launches preserve the calling terminal descriptors and report the actual child exit status. Package directories, required SDK/config files and time-sync files must be accessible to the execution identity. Default runtime paths are <runtime_root>/<execution_uid>/<instance_id>. The provisioned shared runtime root is root:IPC-group mode 03770; UID/instance/log directories are execution-UID:IPC-group 0750, and logs are 0640. Existing foreign-owned leaves require an explicit migration and are never adopted. Control home, passwords, overlays and token state must remain inaccessible for package writes.
Membership in the shared runtime IPC group authorizes access to managed shared-memory and semaphore objects. This group is a runtime communication boundary; it does not provide per-package confidentiality or full tenant isolation. Cgroup and process-principal checks protect manager authority, leases and keys. The separate package account does not isolate mutually hostile packages that share IPC-group access.
Authorize an exact device
A manifest declaration reserves an operator-approved device; it does not authorize the root broker to open arbitrary nodes. No device paths are granted by default. For a gateway using a serial device, identify its stable /dev/serial/by-id/... name and authorize only the required access:
sudo <prefix>/runtime/bin/r2kernel-resourced \
--grant-device /dev/serial/by-id/REPLACE_WITH_ACTUAL_DEVICE \
--device-read --device-write
sudo systemctl restart r2kernel-resourced
The root-only command validates a character/block device and writes an exact-path grant atomically. It does not accept wildcard grants. Restart after a policy change, then claim or launch the package again. Configure its resources.devices.serial or resources.devices.paths with the same normalized path. Tutorial and gateway deployments using managed devices require this explicit operator step; installation alone grants no hardware access. Remove or narrow a grant by editing the root-managed policy and restarting the broker.
Relative aliases within /dev, including typical serial/by-id symlinks, are supported. Absolute symlink targets, traversal, magic links and ordinary files are rejected. Both direct resource claims and package launches validate the device boundary. Admission pins file type, device number, filesystem device and inode. FD opens resolve beneath /dev, check the admitted identity before device-open side effects, reopen the pinned descriptor and inspect the actual returned FD. Read/write requests must fit the operator grant. Hotplug or alias replacement requires a new lease; an old lease cannot open a replacement node. Existing descriptors follow the kernel device lifetime and are not revoked by a policy edit.
The broker policy controls broker-issued descriptors. Direct-device packages still depend on their separate OS device permissions; do not assign a broad device group to a package when broker-only access is intended.
Approve a link-layer capability grant
A resources.link_layer claim lists CAP_NET_RAW, and optionally CAP_NET_ADMIN, in grant.capabilities. The manifest alone grants nothing. The resource service starts the package with a capability only when the root-managed policy approves that capability for the claimed interface. No capability is approved by default. Add an entry to link_layer_grants and restart the broker:
"link_layer_grants": [
{"interface": "enp5s0", "capabilities": ["CAP_NET_RAW"]}
]
sudo systemctl restart r2kernel-resourced
interface is the exact interface name of the claim. When the claim names its
interface with a {{params.NAME}} placeholder, this is the adapter the instance
selected (for example --set fieldbus_nic=enp5s0 needs an enp5s0 entry); an
approval never matches the placeholder itself. capabilities lists CAP_NET_RAW, CAP_NET_ADMIN or both. An entry with an unknown key, an invalid interface name, an empty capabilities list or another capability makes the policy fail to load. There is no command-line option for this field; --grant-device keeps it when it rewrites the policy. Without a matching entry, the launch is refused before any runtime directory, log or reservation exists, with:
link-layer capability CAP_NET_RAW for interface `enp5s0` requires a root-provisioned approval: add {"interface": "enp5s0", "capabilities": ["CAP_NET_RAW"]} to link_layer_grants in the resource authority policy and restart r2kernel-resourced; CAP_NET_RAW is granted host-wide, for every interface
An entry approves the capability for every package that claims that interface. To remove or narrow an approval, edit the policy and restart the broker.
An approved capability is ambient in the package process. It applies to the whole host, not to the claimed interface:
CAP_NET_RAWlets the package open raw and packet sockets on every interface.CAP_NET_ADMINamounts to host network administration: addresses, routes, firewall rules and interface state.
The exclusive mode keeps other R2Kernel leases off the interface; it does not keep the package off other interfaces. The declared protocols are labels, and nothing restricts the package to them. Approve link-layer grants only for packages you trust with the host's network.
Managed scope retirement
Stop, kill, release and reap retain the exact reservation, device grants and
process identity until hierarchical cgroup.events reports populated 0 and
the owned scope directory has been removed. A leader exit alone does not prove
that descendants have exited. Quiescence and scope removal have a separate
two-second cleanup budget, in addition to stop's TERM/KILL grace periods.
After quiescence, only EBUSY permits a scope-removal retry. Other errors or an
incomplete retirement fail explicitly and retain the owned state rather than
reporting a successful release with a warning.
The broker reaps its exact owned Child; allocator cleanup does not use
wildcard waitpid or adopt processes from the root subtree. This does not
authorize killing unknown scopes or weaken manager and device admission.
SDK shutdown may still need a live proof of the Control manager's identity. The broker services that verification while a serialized managed stop waits for its child to exit; it does not bypass authentication or cache a successful proof. Unavailable mutation admission explicitly reports that the request was not dispatched. Request framing remains bounded and shared, not an unconditional proof-latency guarantee.
Durable managed launch and replacement recovery
Control writes .pending-package-launch.json in packages.install_dir before
declaring a lease-bound binding intent or dispatching a managed launch. The
record owns the exact random lease ID and the original instance request
(including omitted/default parameters, bindings and resource overrides).
Registry publication includes file and directory synchronization. A write,
file-fsync, rename or directory-fsync error does not relinquish the pending
lease/request or discard its binding intent. A registry that was already
published when an error occurred is synchronized again during reconciliation;
the journal is retired only after the registry durably owns that request.
Journal retirement failure remains an error, not a successful launch response.
The next package-store operation reconciles the pending exact lease, never
replays the launch RPC. A matching live broker allocation is validated against
the installed directory, manifest and original request before registry
publication. If no allocation is visible, Control stops that exact lease to
cancel any broker preparation still in flight. Only confirmed completion or
typed not_found permits retirement. Broker unavailability, uncertain cleanup,
identity mismatch or an unreadable recovery file preserves recovery ownership
and refuses a new launch. Do not delete these files or resend launch RPCs to
work around an ambiguous outcome.
Restart performs the same side-effect-free launch planning before stopping the
existing instance. Invalid bindings, instance parameters or CPU overrides do not
stop it. The fully merged replacement request and original lease ID are
durably written to .pending-package-restart.json before stop. A stop/start
failure preserves this request. To resume, repeat the package restart with its
instance selector and no new overrides; a different replacement request is
refused while recovery is pending. Uninstall and archive replacement of that
package are also refused until restart recovery completes, so the durable
replacement request cannot lose its package or manifest. Recovery revalidates
the manifest and any already-restored lease, so it neither stops a different
launch nor launches an already-restored replacement twice. Empty resource
override objects mean the same manifest-default request during launch and
recovery. Broker resource admission is still final; a valid local plan is not a
promise that resources will be available after stop.
Stable mutation errors
Broker error replies carry error_code; diagnostic error text is not a
retry, authorization or idempotence contract. Upgrade Control and the broker
together. Missing codes are treated as internal failures, never inferred from
phrases such as “request was not dispatched” or “resource lease not found”.
Only read-only lease proofs use the existing bounded retry policy; a transport
error is never permission to replay a mutation.
Typed package/broker lifecycle errors preserve the same stable HTTP code:
| Code | HTTP | Meaning |
|---|---|---|
policy |
400 | Invalid request or refused package/resource policy |
not_found |
404 | Missing installed package, target or exact lease |
conflict |
409 | Existing instance, allocation or pending replacement conflict |
transport |
503 | Broker transport unavailable; an effectful outcome may be unknown |
not_dispatched |
503 | Broker explicitly refused before dispatch |
store_io |
500 | Package metadata or durability failure; inspect retained recovery state |
internal |
500 | Protocol, execution or cleanup failure; inspect broker/Control diagnostics |
Typed not_found, not its wording, makes exact-lease stop idempotent. An
unknown termination outcome retains its reservation until the broker confirms
retirement; inspect allocations before another mutation.
Recover after a resource daemon crash
Restarting Control preserves leases held by the running resource daemon. A resource daemon startup refuses to proceed whenever aggregate cgroup.events reports processes anywhere in its dedicated root-owned subtree, including root-direct processes and unknown or legacy child scopes. It never signals them or reuses their grants automatically. After confirming that the resource daemon is stopped, root can inspect and explicitly retire those scopes:
sudo <prefix>/runtime/bin/r2kernel-resourced --config <prefix>/runtime/config.yaml --inspect-orphan-scopes
sudo <prefix>/runtime/bin/r2kernel-resourced --config <prefix>/runtime/config.yaml --stop-orphan-scopes
Inspection reports each managed scope and the observed process PID/start time. Recovery requires the daemon's socket singleton lock and a global lock keyed to the cgroup root's device/inode and exact root-owned scope identities, uses atomic cgroup.kill, waits for an empty scope, and removes only those managed scope directories. Missing kernel support or incomplete retirement is an explicit error. Recovery manages only scopes with the exact managed hash name. Unknown or legacy scopes require explicit root administration; they are never killed or adopted automatically. Restart the resource daemon and launch packages again after recovery. Before migrating a legacy installation, stop its existing package instances; legacy instances outside the newly provisioned scope are not automatically adopted or killed.
The read-only verify_control_manager_peer broker query reports only a Boolean manager proof and supports direct or unleased SDK clients allowed to access the root socket. Manager mutations, inspection and federation-key access retain the full manager principal checks. Broker input is limited to one complete JSON line, 1 MiB and a five-second deadline, including descriptor-bearing requests.
Slow or incomplete clients and pending brokered network connections have
bounded lifetimes and do not monopolize unrelated clients. At most 16 brokered
TCP connects are pending at once, and at most 4 per lease; a further connect is
refused before a socket is opened (lease already has the maximum number of
pending TCP connects; request was not dispatched). Admission capacity
exhaustion is an explicit error before resource mutation begins. A pending
network connection uses its original request deadline and is cancelled when
that request expires or disconnects; a returned descriptor still requires the
same live lease and exact process identity. An ambiguous failure is not
permission to replay an effectful request automatically.
Managed child environments remove the configured federation key, API-token and Console password variables, including their default names, in detached and foreground paths. These manager credentials are reserved for manager entrypoints and are also removed from manifest overrides. Package-specific credentials require separate variable names and explicit package configuration.
Packages inherit only public bootstrap settings (PATH, locale, managed Python/library/spec and runtime IPC configuration) plus their manifest environment and broker-issued runtime identity. Private ambient manager variables are omitted. HOME, USER and LOGNAME come from the provisioned execution account. Configure application-specific environment explicitly in its manifest.
Verify the installed package boundary
Use an operator-provisioned integration host and a trusted probe package. Do not change accounts, service policies or kernel settings merely to make a check pass.
- Launch the probe in detached mode and through root/Control foreground launch. Check its real, effective, saved and filesystem UIDs against the configured non-root package UID, which must differ from Control's UID.
- From the package, attempt broker status, allocation inspection, lease mutation and federation-key read, including by executing an approved manager binary. All manager requests must be denied while runtime bootstrap still works.
- Check that configured and default manager credential variables are absent, and that no manager socket, key file or non-stdio descriptor reaches package exec. Ordinary non-root foreground launch preserves the operator UID but marks every descriptor above stdio close-on-exec before package code runs.
- After an interrupted managed launch or restart, inspect the exact lease and retained recovery state before another mutation. Recover using the documented package operation; never delete a recovery journal or replay an ambiguous launch request to bypass a refusal.
These checks describe the deployment contract, not evidence that a particular installation has passed it.
Privileged time-sync storage
Managed time-sync services require R2KERNEL_TIME_SYNC_CONTROLLER_UID (the actual Control UID) and R2KERNEL_RUNTIME_IPC_GROUP in their root-protected units. The override directory /run/r2kernel/time-sync belongs to Control:IPC-group with mode 0750; timesyncd.override.json belongs to Control:IPC-group with mode 0600. The root supervisor's log directory belongs to root:IPC-group with mode 0750, and its status/log files use 0640; the unit uses UMask=0027. IPC members, including ordinary operators and the package account, may read shared root-owned status through the protected output policy. Only root may write those output files; the controller/root override policy remains separate. Packages cannot replace overrides or output files. Root override writes preserve the provisioned Control owner.
The supervisor rejects symlinks, non-regular or multiply linked files, unexpected owners/groups, writable ancestors and legacy permissions before consuming an override or opening output. Missing overrides are optional; malformed or unsafe overrides fail explicitly, even when stale. Upgrade unsafe legacy directories/files only after an operator reviews their contents and explicitly restores the documented owners and permissions. The installer does not silently adopt those objects. A missing privileged supervisor does not permit Control to widen root log permissions or select another output path.
Direct user/root runs without the controller environment use directories owned by the effective UID and private files (0600). Existing shared or foreign-owned files need explicit migration. Disabled and logical profiles do not create an otherwise unused log directory. These storage checks do not alter clock selection or synchronization algorithms.