cloudfox-coreshift-core 2.9.0

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

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.8.5] - 2026-08-13

### Fixed

- **spawn**: `ManagedProcess::pid()` no longer panics after completion — the
  pid is captured at spawn time and remains queryable once the running handle
  is consumed.
- **spawn**: output-limit overflow on the forced-close path (timeout or
  cancellation with a wedged pipe) now returns the partial output with the
  `timed_out` flag instead of `EOVERFLOW`, matching blocking `spawn`.
- **spawn**: dropping an unfinished `ManagedProcess` no longer blocks forever
  when the child is stuck in uninterruptible sleep — the reap wait is bounded,
  and a child already reaped by `poll_completion` is never signalled (its pid
  may have been recycled).
- **spawn**: `Process::kill` and `Process::kill_group` reject non-positive
  pids/pgids with `EINVAL`; `kill(-0)` would have signalled the caller's own
  process group.
- **spawn**: `ProcessGroup` with `isolated=true` and a non-zero custom leader
  is rejected up front with `EINVAL` on the fork backend — `setsid` puts the
  child in a new session, so `setpgid` to an outside leader always failed with
  a confusing `EPERM` at spawn time.
- **spawn**: `fork` fd policies (`CloseFrom3`, `Allowlist`) are now enforced
  in the child after `fork()` via `getdents64` into a fixed buffer, closing
  the TOCTOU where a descriptor opened between the parent-side snapshot and
  `fork()` leaked into every child. The child scan reads every batch through
  EOF (an fd in a later batch can never leak, however many descriptors are
  open), retries an interrupted scan, and aborts the spawn on a scan failure
  instead of failing the policy open.
- **spawn**: a setup-error report interrupted by a signal is retried instead
  of aborting with a partial message, which the parent would have misread as a
  successful spawn.
- **io**: `DrainState::register_with_reactor` is idempotent and no longer
  destroys a slot (closing its fd and losing the stream) when a second
  registration is attempted or a reactor add fails.
- **binder**: dropping a `DeathRecipient` is now memory-safe even when its
  `on_died` delivery is racing the drop on a binder pool thread. The callback
  box lives in a process-wide slab behind an `Arc` (the framework cookie is a
  stable slot index), so the NDK's asynchronous `unlink` can no longer free a
  callback the trampoline is executing; a stale delivery finds an empty slot
  and no-ops.

## [2.8.6] - 2026-08-14

### Fixed

- **binder**: `serve` dispatch now holds a per-`ServeCtx` mutex while invoking
  the user handler instead of sharing one unsynchronized `ServeHandler` across
  binder pool threads. UID/PID are captured before the lock (readers never wait
  behind it), the lock is dropped before the kernel reply is transmitted, and a
  poisoned mutex is recovered in place.
- **binder**: the attribution header wire layout is now documented and verified
  against AOSP SDK 34 — `BUNDLE_LENGTH` is 52 (length excludes the parcel
  magic, matching `writeToParcelInner` backpatching), and the SDK 34
  `AttributionSourceState` carries **no** `deviceId` field.
- **spawn**: a child stuck in uninterruptible sleep (D-state) no longer spins
  the reap loop forever. A SIGKILL is sent only while the child is unreaped,
  and after `D_STATE_REAP_BOUND` (500ms) of D-state the spawn returns
  `timed_out` with partial output; the kernel reaps the child once it leaves
  D-state. Both the blocking and `ManagedProcess` paths are covered, and the
  cancel/drop paths never signal a child already reaped (pid-recycle guard).
- **spawn**: pipe ends are created blocking and relocated to fd >= 3 via
  `F_DUPFD_CLOEXEC` before dup2 into the child, so child stdio 0/1/2 is never
  clobbered; only the parent-facing drain ends are flipped to `O_NONBLOCK`
  after spawn (the child must never inherit a non-blocking stdio — it would
  silently truncate output on `EAGAIN`).
- **spawn**: `make_pipe` / `make_cloexec_pipe` no longer leak descriptors when
  the second relocation fails under fd pressure (EMFILE) — the relocated first
  end and the still-open original are closed on the error path.
- **reactor**: the signalfd-mask restore in `Drop` now checks `pthread_equal`
  against the setup thread and only touches the mask on the thread that
  installed it. A cross-thread drop leaves the setup thread's SIGCHLD block in
  place (delivery is not lost) and never mutates the dropping thread's mask.
- **dex**: parsing is now bounded against hostile inputs: encoded values are
  skipped with a 64-level depth cap, `method_param_types` caps the parameter
  count to the remaining buffer, and string/type/field/proto/method/class-def
  section sizes are clamped to `room / element-size` instead of trusting the
  declared count (which previously could abort on a `with_capacity` OOM).
  Legitimate DEX files are unaffected — the caps only bound absurd declared
  counts.
- **property**: `system_set` now returns `io::Error::last_os_error()`. Bionic's
  `__system_property_set` returns `-1` and sets `errno` (it does **not** return
  a negated errno), so the previous `from_raw_os_error(status)` constructed an
  `EINVAL`-ish error and swallowed the real cause (e.g. `EACCES`).
- **fs**: `mmap_madvise_raw` stats the fd and clamps the mapping length to
  `st_size - offset` before mapping, so a `touch` at the end of the file can
  never raise `SIGBUS` past EOF. The alignment check still runs first, and the
  post-clamp empty mapping is handled.

### Security

- **dex**: the `with_capacity` OOM abort on a hostile declared section count
  could crash the daemon (DoS); the depth cap similarly bounds recursion on a
  deeply nested encoded value. Both are now hard-bounded (see above).

## [2.7.0] - 2026-08-13

### Added

- **transport**: the command/watcher transport seam. `TransportEvent<C, R>`
  (`Command { cmd, calling_uid, reply }`, `WatcherClosed`, `WatcherDied`),
  `ReplySink<R>` (one-shot reply target, consumed exactly once), and the
  persistent `WatcherId` / `WatcherSink<U>` pair. The message-shaped boundary
  between transport-agnostic channel logic and transport backends (the
  abstract-socket daemon today, a Binder backend later); generic over the
  domain command and reply types. Client (`ReplySink`, one-shot) and watcher
  (`WatcherId`, long-lived) are distinct concepts; a watch registration is a
  command, so watcher-registry mutation is single-owner by construction.

## [2.6.0] - 2026-08-13

### Added

- **binder::Handoff**: the content-provider handoff client. Resolves an app's
  exported provider through `IActivityManager.getContentProviderExternal`,
  walks the `ContentProviderHolder` reply parcel (the full ProviderInfo →
  ApplicationInfo chain, pinning the app's `uid` from `ApplicationInfo.uid`
  with no extra IPC), hands a service binder to it via
  `IContentProvider.call("sendBinder", …)`, and releases it with
  `removeContentProviderExternalAsUser`. Tx codes for the lifecycle are
  resolved fresh from `framework.jar` DEX (`dex::resolve_handoff_codes`);
  `CALL_TRANSACTION` is the stable constant 21. Wire target: Android 14 /
  SDK 34. See `docs/BINDER-EXEC-DILEMMA.md`.
- **binder::sys**: general parcel-skip primitives shared by the handoff walk —
  `skip_bundle`, `skip_char_sequence`, `skip_sparsearray`,
  `skip_typed_object_array`, `skip_string8_array`, and a `skip_value` covering
  the full `Parcel.writeValue` tag table (`VAL_IBINDER = 15`,
  `VAL_INTARRAY = 18`, …); `ParcelReader::owned` constructor for reply
  cursors.

### Changed

- **binder**: module wiring for `handoff` and `serve` (server-side primitives),
  with matching non-Android stubs so the crate stays type-checkable off-device.

## [2.5.1] - 2026-08-13

### Changed

- rustfmt normalization across the split binder/spawn modules (import
  ordering, line wrapping, module declaration ordering, one duplicated
  `#[cfg]` attribute). Cosmetic only; no behavior change.

## [2.5.0] - 2026-08-13

### Added

- **spawn**: `spawn_managed` and `ManagedProcess` let a caller-owned reactor
  drive the full child lifecycle without blocking. Core retains ownership of
  stdout/stderr draining, exact output-limit reporting, timeout and explicit
  cancellation, TERM-to-KILL escalation, process-group signaling, and
  `waitpid` reaping; callers route readiness events and poll at
  `next_deadline()`.

### Changed

- **spawn**: split into per-backend modules — `exec` (exec-family helpers),
  `posix`, and `fork`. The public API is unchanged; only the module layout
  moved.
- **binder**: split into per-service modules — `sys`, `am` (activity
  manager), `display`, `fps`, `task_stack`, and `raw`. The public API is
  unchanged; only the module layout moved.

### Fixed

- **spawn**: dropping an unfinished `ManagedProcess` now sends SIGKILL to its
  configured process target and reaps the child. Reactor-registration failures
  and other abandoned setup paths cannot leave a live child or zombie behind.
- **spawn**: process-group leader `0` is normalized to the spawned child PID
  when signaling. POSIX `setpgroup(0)` creates a child-owned process group;
  treating zero as a literal kill target could signal the caller's own group.

## [1.2.40] - 2026-08-11

### Changed

- **Binder**: the `FpsListener` wake eventfd is now created with
  `EFD_NONBLOCK`, matching the obs/fgproc observer eventfds. The callback only
  ever writes to it (an eventfd write never blocks), while epoll-based
  consumers register it edge-triggered and drain to EAGAIN — a blocking fd
  wedged the consumer's drain loop (observed on-device: the fps reactor
  stopped serving its socket and never streamed a sample). Consumers that
  still prefer blocking reads can clear `O_NONBLOCK` on their own dup.

## [1.2.36] - 2026-08-10

### Fixed

- **Binder**: a genuine idle 0-FPS report is no longer mistaken for "no report
  yet". `last_fps` used the value's bit pattern as its own sentinel —
  `0.0f32` is bit pattern 0, so an idle `onFpsReported(0)` read as `None`:
  the sample was dropped for watchers, and (downstream) the first-report-after-
  task-swap drop stayed armed and discarded the next real measurement. A
  publish-time `FPS_SEEN` flag now disambiguates the states; the decision is
  factored into a testable helper with a regression test.

## [1.2.35] - 2026-08-10

### Fixed

- **spawn**: a timed-out child with a custom process-group leader is now
  actually signaled. `wait_loop` targets the child's effective pgid
  (the configured leader, or the child's own pid after `setsid`) instead of
  `kill(-pid)`, which addressed a group that did not contain the child — the
  SIGTERM/SIGKILL escalation never reached it and the wait could hang
  forever. `Process::kill_pgroup` now delegates to a new explicit-group
  `Process::kill_group`.

## [1.2.34] - 2026-08-10

### Fixed

- **Binder**: input parcels on the write path are RAII-owned. A failed
  `AParcel_write*` between `AIBinder_prepareTransaction` and `AIBinder_transact`
  previously leaked the prepared parcel; `transact_write` now drops it on the
  error path and records the transfer into `AIBinder_transact` (the framework
  deletes the input parcel even on failure), so the wrapper never deletes
  twice. Reworked the observer, foreground-process observer, display-callback,
  FPS, and task-stack transactions onto it.

### Security

- **Binder**: parcel string reads are length-bounded. `string_alloc` now
  refuses negative or > 1 MiB advertised lengths instead of driving an
  unbounded `reserve_exact` (OOM abort); the read surfaces as an error.

### Internal

- **drm**: `wait_vblank_ioctl_matches_known_constant` writes the expected
  ioctl via a `u32` literal so the test compiles on the Android target (where
  `libc::Ioctl` is `i32`).

## [1.2.33] - 2026-08-10

### Fixed

- **Binder**: `FpsListener` wake routing is now actually functional. The
  per-instance eventfd was handed to `AIBinder_new` as the callback binder's
  args, but `fps_on_create` still returned `null`, and `AIBinder_getUserData`
  returns exactly what `onCreate` returns — so the wake could never fire. The
  callback `onCreate` now passes the userdata through (matching
  `TaskStackListener`) and `onDestroy` reclaims the box. Released 1.2.32's
  per-task FPS reporting regressed to silent (event never signalled); this
  restores it.

## [1.2.32] - 2026-08-10

### Fixed

- **Binder**: `FpsListener` now routes its wake eventfd **per-instance**
  through the callback binder's userdata (`AIBinder_getUserData`), matching
  `TaskStackListener`. The old process-wide `FPS_EVENTFD` static was
  overwritten on every `open()`, so a re-opened listener rewired an earlier
  registration's `onFpsReported` wake into the newest eventfd (or dropped
  it). Each instance now owns its own fd and `AIBinder_new` failure reclaims
  it instead of leaking.
- **Binder**: `FpsListener` and `TaskStackListener` now **deregister
  best-effort on drop**, so a dropped listener no longer leaves the framework
  delivering `onFpsReported` / task-stack wakes forever. The local strong ref
  on the callback binder is intentionally not released (safe-by-leak) so the
  per-binder userdata can never be reclaimed while a callback is in flight.

## [1.2.31] - 2026-08-10

### Fixed

- **Binder**: `TaskStackListener` now routes its wake eventfd **per-instance**
  through the callback binder's userdata instead of a process-wide
  `TASK_STACK_EVENTFD` static. Previously every `open()` overwrote the
  shared static, so when a single process hosted two listeners (the
  foreground task source and the fps channel) all wake writes went to the
  listener opened last and the other's eventfd never fired. Each listener now
  owns its own fd, resolved in `on_transact` via `AIBinder_getUserData`, and
  reclaimed in `on_destroy`; the `AIBinder_new` failure path no longer leaks.

## [1.2.30] - 2026-08-09

### Added

- **Binder**: New `TaskStackListener` — a push-based task-stack change wake-up
  registered with `IActivityTaskManager.registerTaskStackListener`. The
  `ITaskStackListener` server object signals an eventfd on any stack event
  (`onTaskStackChanged`, `onTaskMovedToFront`, …) and parses **nothing**;
  consumers re-query `getFocusedRootTaskInfo` (txn 31) for the authoritative
  `(taskId, pkg)`. `dex::resolve_task_stack_codes` resolves
  `TRANSACTION_registerTaskStackListener`/`unregisterTaskStackListener` for the
  legacy `ITaskStackListener` interface — the newer
  `ITaskChangeListener`/`registerTaskChangeListener` pair is absent on the
  target ROM.

## [1.2.25] - 2026-08-09

### Added

- **DRM**: New `drm` module with `DrmCard::open` (card node by path) and
  `DrmCard::wait_vblank` — a blocking `DRM_IOCTL_WAIT_VBLANK` wait that
  returns the monotonic unblock instant. Reply timestamps are ignored because
  `msm` display stacks zero `t_sec`/`t_usec`.
- **Reactor**: `Fd::dup` for owned descriptor duplication (fan-out eventfd
  wakeups across threads).

## [1.1.2] - 2026-05-12

### Fixed

- **Spawn**: Removed a 1ms busy-wait loop that occurred after child process termination while waiting for final I/O drainage.
- **Signals**: Corrected `SignalRuntime::unblock_all` to use `pthread_sigmask` for thread-safe consistency.
- **Documentation**: Resolved duplicated documentation headers in the `unix_socket` module.

## [1.1.1] - 2026-05-12

### Fixed

- **Reactor**: Fixed a bug where `EPOLLERR` events were not folded into `readable`/`writable` flags, potentially causing hangs in callers that do not explicitly check `error`.
- **Spawn**: Fixed a potential hang in the process wait loop by explicitly handling `EPOLLHUP` (hangup) events.
- **Documentation**: Cleaned up redundant lines in `proc` module documentation.

## [1.1.0] - 2026-05-12

### Added

- **Reactor**: Exposed `add_with_flags` for custom epoll registration.
- **Unix Sockets**: Added `accept_timeout` for millisecond-based timeouts.
- **Unix Sockets**: Added `peer_cred` support for retrieving peer process identity.
- **Signals**: Added `register_handler` for arbitrary process-wide signal handlers.
- **Signals**: Added `signalfd_new` for reactor-compatible signal reception.

## [1.0.0] - 2026-05-12

### Added

- **Logging**: Refactored to a backend-agnostic facade with `Logger` instances.
- **Reactor**: Added `hangup` field to `Event`.
- **Documentation**: Comprehensive behavioral contract and architectural invariant documentation.

### Changed

- **Spawn**: `SpawnBackend` is now mandatory at builder construction.
- **Layering**: Renamed `blocklist_fingerprint` to `path_fingerprint`.

### Stability Contract

The 1.x series guarantees stability for the following behavioral contracts. Changes to these are considered **breaking changes**:

- **Reactor Events**: The mapping of epoll flags to `Event` fields (`EPOLLERR` -> `error`, `EPOLLHUP` -> `hangup`).
- **Reactor Timeouts**: The `-1`/`0`/`positive` millisecond contract in `Reactor::wait`.
- **Descriptor Defaults**: The use of `O_CLOEXEC` / `SOCK_CLOEXEC` for all Core-created descriptors.
- **Signal Compatibility**: The requirement to use `signalfd` (via `signalfd_new`) for reactor-compatible signal handling.
- **Policy Neutrality**: The architectural commitment to provide primitives without policy.

## [0.3.0] - 2026-05-11

### Added

- Added Unix socket peer credential support through `SO_PEERCRED`.

## [0.2.0] - 2026-05-10

### Added

- Added low-level mmap/madvise preload primitive with page-aligned offset validation.

## [0.1.0] - 2026-05-04

### Added

- Initial official CoreShift Core release.
- Primitive Linux/Android APIs for process spawning, process lifecycle, process
  I/O draining, procfs parsing, filesystem helpers, UID/GID/path identity,
  readahead, signals, inotify, epoll/reactor use, eventfd, timerfd, signalfd,
  and Unix domain sockets.
- Explicit spawn backends: `Fork` and `PosixSpawn`.
- Explicit file descriptor inheritance policy through `SpawnFdPolicy`.
- Low-level abstract and pathname Unix stream socket primitives.

### Notes

- Core is policy-free and runs the exact argv it is given.
- Core does not choose shell, root, package, foreground, daemon, fallback, or
  product behavior.
- Unsupported backend/option combinations return errors instead of selecting a
  different backend.