cloudfox-coreshift-core 2.29.1

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
# CoreShift-Core Architecture

CoreShift-Core is the primitive layer for the CoreShift stack. It wraps
Linux/Android facilities with small Rust APIs and leaves product decisions to
higher layers.

## Role

Core owns:

- Process spawning primitives and explicit file descriptor policy.
- Process lifecycle helpers.
- Stream draining and bounded output capture.
- Procfs and UID/GID/path identity helpers.
- Signals and reactor primitives.
- Inotify watch/decode helpers.
- Unix domain socket primitives.
- Filesystem preload primitives such as readahead and mmap/madvise.
- kobject uevent and DRM vblank primitives.
- A command/watcher transport seam for channel logic.

Core returns structured errors from the underlying platform. It does not turn a
failed primitive into a policy decision, retry plan, fallback command, or Android
product behavior.

## Boundaries

Core does not own:

- Android package discovery.
- Foreground package or process decisions.
- Daemon command-line behavior.
- Socket message protocols.
- App allowlists, blocklists, or preload policy.
- Android default paths.

Those choices live in Engine, Policy, or product packaging layers.

## Core Guarantees

Future contributors must ensure Core maintains these invariants. Each exception
below is named and rationalized — the exceptions are intentional, not
documented-by-omission.

- **No Policy Decisions**: Core provides primitives, not behaviors. It does not choose package allowlists, retry strategies, or product-specific defaults. (One build-mode caveat: the logging facade's release build strips sub-`Error` levels — see Logging.)
- **No Android-specific Behavior**: Core uses Android syscalls and properties when running on Android, but it does not implement higher-level Android product logic (like foreground app detection).
- **No Hidden Threads**: Core performs work on the caller's thread. It does not spawn background maintenance threads or global worker pools. Named exceptions, each with its rationale:
  - `binder` — one process-lifetime NDK binder pool thread (`ABinderProcess_joinThreadPool`) receives framework callbacks; the NDK requires it, and it exists only when a binder service is opened (`binder/sys.rs:989-999`).
  - `spawn` — the orphan-reaper thread (`spawn-orphan-reaper`, `spawn/mod.rs:101-151`) reaps children whose caller will never `wait`: `wait=false` spawns, D-state/cancel-timeout give-ups, and `ManagedProcess::Drop`/`RunningProcess` give-ups. It is required by the `wait=false` detach contract — a long-lived daemon that detaches children would otherwise accumulate zombies and exhaust the pid space (REVIEW-FINDINGS.md:15). It starts lazily on the first orphaned child, polls registered pids every 250 ms, and deliberately reaps only registered pids (not a global `waitpid(-1)` loop, which would race callers explicitly waiting on other children). It also sweeps orphaned *sessions* registered by the D-state give-up (REVIEW-FINDINGS.md:H2) — keeping SIGKILL until `/proc` empties — so giving up on an unreapable leader can never leak its contained members. **The session arm is a global sweep, not a per-pid wait:** it `read_dir`s `/proc`, matches every process whose session id equals a registered sid, and `kill(-pgid, SIGKILL)`s each live group (session_sweep, spawn/mod.rs:2059+). Each registered sid is paired with the leader's `/proc/<pid>/stat` starttime (field 22) captured at registration: before every sweep iteration the reaper verifies the sid-owner is still the same process incarnation and drops the registration (without killing) the moment the leader is gone or recycled — a bare numeric sid must never SIGKILL an unrelated session whose pid reused the leader's number (D5a).
- **No Global Mutable State**: Core is stateless. Configuration must be passed to primitives via options or arguments. Logging uses an immutable platform default by default; runtime backend selection requires an explicit `Logger` instance. Named exceptions, each with its rationale:
  - `binder` — process-wide statics (eventfds, transaction codes, callback state, `GET_USER_DATA`) because NDK binder callbacks arrive on the pool thread with no caller userdata pointer to thread state through (`binder/sys.rs:1041`, `serve.rs:378-381`, observer statics in `am.rs`/`display.rs`/`fps.rs`).
  - `spawn` — `ORPHANED`, `ORPHANED_SESSIONS`, and `REAPER_STARTED` feed the orphan-reaper (`spawn/mod.rs:87-107`).
  - `signal` — `SHUTDOWN_FLAG_PTR`, a process-global atomic pointer for the shutdown flag (`signal.rs:46`); documented in Signal Handling below.
- **No Capability Enforcement**: Core performs syscalls; it does not implement its own permission or capability model. The one deliberate exception is the anti-TOCTOU ownership gate in `fs`: the `*_nofollow` helpers and `ensure_state_dir` refuse paths whose parent is not owned by the effective uid (`EACCES parent_dir_owner` / `state_dir_owner`, `fs.rs:529-531, 678-680`). This is a swap-safety check on an already-open path, not a policy-based access model.
- **No Scheduler Ownership**: Core provides reactor primitives (`epoll`) but does not include a task scheduler or executor.

## Use From Higher Layers

Higher layers should pass exact descriptors, paths, argv, offsets, byte counts,
and socket names into Core. Core should not infer a package, widen a preload
range for policy reasons, or decide whether a preload is desirable.

When a platform primitive is unsupported, Core returns an error such as `ENOSYS`
so the caller can decide whether to skip, fall back, or fail.

## Subsystems

### Process Spawning (`spawn`)

Core provides explicit control over process creation via
`SpawnBackend` — `PosixSpawn`, `Fork`, `Vfork`, `Clone3`, and
`Clone3Pidfd`.

- **Explicit Backends**: Callers must choose a `SpawnBackend`. Core does not
  silently switch backends based on capability; it returns an error if a backend
  cannot fulfill the requested `SpawnOptions`.
- **FD Policy**: Child file descriptor inheritance is controlled through
  `SpawnFdPolicy`. Core defaults to `CloexecOnly` to prevent accidental
  descriptor leakage.
- **Bounded Output**: Output capture is combined (stdout + stderr) and strictly
  bounded to prevent memory exhaustion by runaway processes.
- **Process Groups**: `ProcessGroup` supports caller-specified leaders and
  isolated groups (`setsid`). `session_containment()` installs a child-side
  seccomp filter that denies `setsid`/`setpgid`/`unshare`/`setns`, locking the
  child into the group Core placed it in. The **pty** containment variant
  (`PTY_SESSION_CONTAINMENT_FILTER`, fork.rs:386-410) instead **allows
  `setpgid`** — job control requires it — while still denying
  `setsid`/`unshare`/`setns`, so a pty child can fragment its session into
  many process groups but can never leave it (kernel `setpgid` is
  session-scoped). The session kill loop therefore enumerates **pgids inside
  the sid** rather than trusting one group.
- **PTY**: `pty()` spawns the child on a pseudo-terminal; the master drains as
  the child's merged stdout+stderr stream, `resize_pty` applies `TIOCSWINSZ`,
  and `write_input`/`write_input_nonblock` feed stdin. The pty slave is left
  at the kernel-default cooked line discipline (`ISIG|ICANON|ECHO|IXON` on,
  `IUTF8` off): Core is no-policy and does **not** configure termios — the
  caller owns it (tcsetattr on the slave). Termux-style terminals must keep
  kernel echo (never locally echo) unless the caller switches raw mode.
- **Natural-exit policy**: `SessionExitPolicy` on `SpawnOptions` decides what
  happens when a contained session's leader exits on its own.
  `SessionExitPolicy::Sweep` (default) gates completion on the session sweep
  emptying `/proc` — leader-reap is the sweep trigger, not master EOF, so a
  background member holding the pty slave open cannot stall completion (A4-3);
  the sweep SIGKILLs such contained members. `SessionExitPolicy::LetMembersSurvive`
  reports completion on leader-reap without signaling the session (nohup-style
  background jobs keep running).
- **Lifecycle**: blocking `spawn()` and async `spawn_managed` /
  `spawn_start` with caller-owned reactors (`register_with_reactor`,
  `poll_completion`, `next_deadline`, `request_cancel`, `kill_grace_ms`,
  TERM-to-KILL escalation).

### Reactor and I/O (`reactor`, `fd`, `io`)

Core implements a lightweight, edge-triggered `epoll` reactor.

- **Non-blocking by Default**: Reactor primitives are designed for non-blocking operations.
- **Explicit Readiness**: Callers must drain descriptors until `EAGAIN` to satisfy the edge-triggered contract.
- **Stateless Orchestration**: `DrainState` manages the bookkeeping of multiple process pipes without owning the reactor or the thread.
- **Fd**: `fd::Fd` is the owned descriptor wrapper shared across modules
  (`eventfd`, `timerfd`, `dup`, `read_u64`, …).
- **Bounded Output**: `max_output` caps captured output; `resume_stdout` /
  `resume_stderr` support pausing and resuming a stream under backpressure.

### Signal Handling (`signal`)

Core provides a centralized shutdown coordination mechanism.

- **Shared State**: `install_shutdown_flag` uses a process-global atomic pointer to signal shutdown to the caller's main loop.
- **No Signal Policy**: Core installs handlers for `SIGINT` and `SIGTERM` but does not decide how the application should exit.
- **SignalRuntime**: `SignalRuntime` provides `signalfd_new`, `register_handler`,
  `block_current_thread` / `restore_current_thread`, `wait`, and
  `interrupt_thread` for thread-targeted signals.

### Logging (`log`)

Logging is a backend-agnostic facade designed for zero global mutable state.

- **Immutability**: The default logging path uses compile-time dispatch to the platform's primary backend (Android `liblog` or `stderr`).
- **Explicit Instances**: Dynamic backend selection (e.g., for silencing specific components or redirection) requires an explicit `Logger` instance.

### Unix Sockets (`socket`)

Abstract and pathname Unix domain stream sockets.

- `UnixListener` (bind/accept/`accept_timeout`), `UnixStream`
  (connect/`peer_cred`/`check_connect_error`), `socketpair`, `chmod`.
- `StaleSocketPolicy` for stale-socket cleanup on bind.

### Procfs and Filesystems (`proc`, `fs`)

- `proc`: `/proc/<pid>/status` (`ProcStatus`), `/proc/<pid>/cmdline`, `stat`,
  `uid` / `uid_at`, `path_uid`, `ProcDir` (owned dirfd for race-free probing),
  `clock_ticks_per_second`, `chown`.
- `fs`: `read_to_string`, `readahead`, `fadvise`, `mmap_madvise`
  (page-aligned, `MADV_WILLNEED`, optional touch), and symlink-safe helpers
  (`read_nofollow`, `write_atomic`, `open_append_nofollow`,
  `remove_nofollow`, `ensure_state_dir`).

### Inotify, Uevent, DRM (`inotify`, `uevent`, `drm`)

- `inotify`: `init`, `add_watch`, `remove_watch`, `read_events`,
  `decode_events`; `InotifyEvent` + packaged watch masks (`PACKAGE_FILE_MASK`,
  `PARENT_WATCH_MASK`, `MODIFY_MASK`, `QUEUE_OVERFLOW_MASK`, `IGNORED_MASK`,
  `UNMOUNT_MASK`, `DELETE_SELF_MASK`, `MOVE_SELF_MASK`). See INOTIFY.md.
- `uevent`: kobject uevent socket `open`/`recv` and `drain_battery` (drains the
  whole queue, returns the last `power_supply` event). Primitive only —
  currently no in-tree consumer; it precedes callers by design.
- `drm`: `DrmCard::open` (card node by path) and `wait_vblank` (blocking
  `DRM_IOCTL_WAIT_VBLANK` returning the monotonic unblock instant). Primitive
  only — no in-tree consumer; it precedes callers by design.

### Transport Seam (`transport`)

A message-shaped boundary between transport-agnostic channel logic and
transport backends.

- `TransportEvent<C, R>` (`Command { cmd, calling_uid, reply }`,
  `WatcherClosed`, `WatcherDied`), `ReplySink<R>` (one-shot reply target),
  and `WatcherId` / `WatcherSink<U>` (long-lived watcher push).
- Generic over the domain command and reply types; the abstract-socket daemon
  uses it today, a Binder backend can later.
- No wire encoding lives here — backends serialize/deserialize. `WatcherId` is
  allocated monotonically by the registry owner; `calling_uid` is `None` when
  the transport exposes no caller identity.

### Process primitives (`process`)

Raw syscall wrappers for the current process and child setup (used by `spawn`):

- `fork()` (unsafe) → `ForkResult`; `setsid()`, `setpgid(pid, pgid)`.
- `setuid`/`setgid` via `setresuid`/`setresgid` (real + effective + saved),
  `getuid`/`getgid`, `set_pdeathsig` (sent when the parent *thread* that
  created the task exits — see PROCFS.md), `close_fds_from(start)`.
- `redirect_stdio_to_devnull()` and `redirect_fd_to(src, dst)` (both unsafe) —
  dup2 helpers for daemon hygiene. See PROCFS.md for the full surface.

### Binder (`binder`, Android-only)

NDK binder client and serving primitives (`dlopen`ed `libbinder_ndk.so`,
no NDK link-time dependency). On non-Android targets every public type is
mirrored by a stub implementation (`binder/mod.rs`) so the whole API surface
stays type-checkable off-device; stubs return
`binder(-1, "binder:unsupported platform")` (or equivalent), and some variant
methods (e.g. `send_binders`) exist only in the Android implementation.

- **Clients**: `ActivityManager` (`open`, `open_with_observer`,
  `open_with_fgproc_observer`, `open_with_uid_observer`, `get_focused_task`,
  `get_focused_package`, `get_focused_task_id`), `DisplayManager`
  (`open_with_callback`, `is_interactive`, `info`), `FpsListener`,
  `TaskStackListener`, `RawBinderService`, `Handoff` (`handoff`,
  `handoff_binders`).
- **Tx codes** are resolved at runtime from `framework.jar` DEX
  (`android::dex`) by default. This is the rule for every code this crate
  transacts with: `ActivityManager`, `DisplayManager`, `FpsListener`,
  `TaskStackListener`, uid/foreground observers, and Handoff's content-provider
  calls.
- **One deliberate exception**: `handoff.rs` keeps `CALL_TRANSACTION = 21`
  (`IContentProvider.call`, `FIRST_CALL_TRANSACTION(1) + 20`) as a hardcoded
  constant because it is a stable Binder/AIDL framework transaction constant
  that is **not represented in the DEX metadata** (verified on-device:
  `IContentProvider$Stub` defines no `TRANSACTION_*` fields). This exception is
  safe because the value is part of the AIDL contract for `ContentProvider.call`
  and has not changed across Android versions; re-resolving it from DEX is
  impossible, not merely unnecessary. Do not "fix" this constant back into DEX
  resolution — there is nothing to resolve.
- **Serving**: `serve` provides `ServeCall` (code, caller uid/pid, request/reply
  parcel cursors) dispatch with per-handler serialization and an admission gate
  (`max_inflight`, over-capacity rejected up front); `wire` models the A1 handoff
  bundle layout host-side.

### Android (`android`, Android-only)

- `property`: `get`/`set` (direct `__system_property_get`/`set`), `find`,
  `read`, `serial`, `wait`, plus the `AndroidPropertyStore` trait and
  `SystemAndroidPropertyStore` default implementation. Non-Android builds
  degrade gracefully (`get`/`find` → `None`, the rest → `Unsupported`).
- `dex`: minimal ZIP + DEX parser resolving `TRANSACTION_*` static int fields
  from `framework.jar` (STORED entries only, largest-first). Public resolvers:
  `find_transaction_code`, `resolve_tx_codes_from_dex`,
  `resolve_display_info_tx`, `resolve_is_interactive_tx`,
  `resolve_display_register_callback_tx`, `resolve_fgproc_codes`,
  `resolve_fgproc_codes_fallback`, `resolve_fps_codes`, `resolve_handoff_codes`,
  `resolve_task_stack_codes`, `resolve_uid_observer_codes` → `UidObserverCodes`.
  The JAR is re-read on every call (no caching in `dex` itself); every failure
  collapses to `None`.

### Supervisor / Child-Ownership Contract (A17-03)

Core's `spawn` primitives give a **live** daemon total session ownership: the
session sweep, the orphan-reaper's registered-sid arm, and `pdeath_signal`
(`set_pdeathsig`, `process.rs:92-97`) keep every contained session killable for
as long as the daemon process itself lives. PDEATHSIG is **leader-only partial
mitigation** — it is retained across a non-secureexec `exec` on this kernel, but
it signals only the direct leader, never the session members a leader may have
detached into their own groups.

Core does **not** and cannot close the daemon-death gap on its own: if the
daemon is `SIGKILL`ed (or the whole process group with it), the orphan-reaper
thread and the session sweeps die with it, and every contained session leaks.
Closing that gap is a **product-layer responsibility** with this contract:

1. **External supervisor**: an out-of-process supervisor must watch the daemon
   (e.g. `/proc/<daemon-pid>` polling or a binder death notification) and, on
   daemon death, **kill the daemon's process group and every journaled session
   sid** the daemon registered. The daemon-side mechanism for journaling is
   deliberately product-owned (a sid journal file or a supervisor IPC), because
   writing the journal is a policy decision — Core only provides the primitive
   building blocks (`proc::ProcDir` enumeration, `spawn` session containment,
   `pidfd`-backed death observation) the supervisor builds on.
2. **Restart discipline**: a supervisor that respawns the daemon must not race
   the fresh instance's socket bind (see A17-06 socket-secret probe in the
   server) and must clear the stale sid journal before respawn so the fresh
   daemon never inherits a dead session's registration.
3. **In-process fallback**: while the daemon is alive, the daemon-owned sweep
   (exec backend) already reaps abandoned streams; the supervisor contract only
   covers the daemon-process-death window.

Until a supervisor ships, this gap is **explicitly tracked as an open
dependency** (owner: FocusSource/CoreShift-Server packaging). `pdeath_signal`
for the direct leader is the only in-tree mitigation today.

### Maintenance Notes

Keep new APIs primitive-shaped. If an API needs Android package metadata,
foreground state, daemon configuration, or product defaults, it belongs above
Core.