loaderd is the native program-loader service for EriX
  • Rust 99.9%
  • Linker Script 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Erik Inkinen a45a7a7173
All checks were successful
CI / markdown (push) Successful in 8s
CI / test (push) Successful in 1m21s
chore: Merge native CLI development into main
Merge the selected feature/native-cli history with an explicit two-parent commit so main retains the development lineage and the validated source snapshot. The resulting tree is identical to the selected feature commit; no dependency pins or runtime behavior are changed by this merge.

Previous main: 6a37eccf1a
Selected feature: 9960bee61b

Publish this integration point following the requested cross-repository merge. Preserve the feature branch and immutable dependency objects for reproducibility; do not squash, rebase or rewrite existing commits.
2026-09-12 08:28:50 +03:00
.github build: Pin dependency and CI helper source commits 2026-09-12 07:40:16 +03:00
src perf: Reuse the verified immutable root digest for handoff 2026-09-08 21:44:24 +03:00
.editorconfig Initial commit 2026-05-22 15:17:29 +02:00
.gitignore Initial commit 2026-05-22 15:17:29 +02:00
.markdownlint-cli2.yaml Initial commit 2026-05-22 15:17:29 +02:00
ARCHITECTURE.md build: Pin dependency and CI helper source commits 2026-09-12 07:40:16 +03:00
Cargo.toml build: Pin dependency and CI helper source commits 2026-09-12 07:40:16 +03:00
CODE_OF_CONDUCT.md docs: Synchronize shared contribution and review policies 2026-09-12 06:38:19 +03:00
CONTRIBUTING.md build: Pin dependency and CI helper source commits 2026-09-12 07:40:16 +03:00
LICENSE Initial commit 2026-05-22 15:17:29 +02:00
linker.ld refactor: remove obsolete workspace end symbol 2026-07-27 13:58:24 +03:00
README.md build: Pin dependency and CI helper source commits 2026-09-12 07:40:16 +03:00
ROADMAP.md perf: Reuse the verified immutable root digest for handoff 2026-09-08 21:44:24 +03:00
rustfmt.toml Initial commit 2026-05-22 15:17:29 +02:00
SECURITY.md docs: Synchronize shared contribution and review policies 2026-09-12 06:38:19 +03:00

EriX Program Loader (loaderd)

loaderd is the native program-loader service for EriX. It is a no-std service binary with an explicit root-owned receiver, a private launchd-only receiver, and required vfsd, procd, and dynlinkd peers.

EriX is a clean-room, capability-based microkernel operating system written entirely in Rust.

Technical requirements are tracked in the EriX requirements, conventions, and project documentation.

See:

  • docs for design documents, specifications, and development plans.
  • Related architecture repositories for kernel, services, libraries, drivers, and integration tooling.

Purpose of This Repository

This repository implements the EriX program-loader daemon. Its purpose in EriX is to authenticate and validate executable source graphs, coordinate complete-graph link planning, stream semantic segments and relocation batches to procd, and hand started or staged launch results back to the authorized caller.

Functionally, it accepts loader IPC requests, resolves ordinary executable paths through the public VFS namespace, and reads file contents through vfsd. Private launchd preparation instead receives one provider-pinned, read-only executable endpoint and uses the canonical path only to select the signed runtime-graph entry. Privileged service preparation carries no request authority. The root receiver admits only canonical packaged ttyd, powerboxd, and launchd tuples; a separate deviced receiver admits only the canonical drv-rtc-cmos tuple. Each peer has fixed caller-local receipt destinations. Loaderd authenticates the packaged runtime graph at the exact path and digest supplied in its startup envelope, validates every selected ELF64 ET_DYN object, submits the complete closure to dynlinkd, and returns deterministic success or failure responses after procd accepts every segment and authoritative relocation batch. The repository keeps the implementation, interface contracts, tests, and documentation for that behavior in one reviewable ownership boundary.

The maintained responsibilities are:

  • validate its startup envelope, nonzero runtime-graph trust root, and exact launchd/vfsd/procd/dynlinkd peers
  • resolve canonical absolute UTF-8 VFS paths through public vfsd calls
  • reject malformed, missing, directory, empty, read-denied, and changed-while-reading discovery sources
  • select arbitrary-size acyclic dependency closures into deployment-sized caller storage without a product object, depth, dependency, or segment cap
  • size each manifest/object prefix from its verified read, retaining both live metadata checks without a redundant size-only VFS traversal; reserve the object index first and return only the disjoint unused arena for segments
  • classify and validate native ELF executable and shared-object kinds without allocator authority
  • retain one exact load bias per graph object: the root keeps its linked address, zero-based shared objects are packed at page-aligned ends of the preceding mapped extent, and executable roots still require a null-page guard
  • accept only raw-nonoverlapping load segments; adjacent segments may share an aligned page when their combined final permissions remain non-W+X
  • provide a target-only no-heap global allocator so dependency crates that expose alloc-backed APIs can link in dynamic artifact builds without granting runtime heap authority
  • reserve explicit target stack space, stream record/segment fragments through canonical IPC-sized buffers, and read executable chunks directly into deployment storage without returning a duplicate transport-sized payload
  • detect static ET_EXEC versus dynamic ET_DYN executable sources and reject executable kinds without a materializer before process handoff
  • submit every object identity, dependency, relocation range, and dynamic table in the selected closure to dynlinkd
  • stream every canonical file-backed segment and each transport-bounded batch of authoritative relocation writes to procd through the ordered semantic materialization ABI
  • expose malformed ELF, invalid load-plan, unsupported executable-kind, and process-handoff failures through typed loader responses
  • request process creation only through the typed procd begin/segment/relocation-batch/commit materialization transaction
  • expose staged preparation only on the private launchd receiver, return one exact install grant, and leave the process unstarted for launchd's staged handoff
  • expose privileged service preparation on two disjoint authenticated receivers: root may name only ttyd, powerboxd, or launchd, while deviced may name only drv-rtc-cmos; return the paired staged endpoint and install grant only at that peer's fixed receipt slots
  • accept exactly one SEND executable-object receipt on private preparation, read root bytes only through that endpoint with exact size and EOF checks, and retire it before replying on every path
  • keep startup and root-owned requests authority-free, accept no private-request authority beyond that fixed receipt, authenticate the exact staged-commit reply, and retire rejected or residual receipts

Source loading remains request-local: no metadata, rights, or object bytes are cached across launches. A later object can exhaust the bounded workspace after earlier objects have been read and closed, but before any dynlink/procd mutation. Every terminal result erases the whole workspace and all three transport arenas; uncertain close still takes precedence and cannot be reported as clean cleanup. Removing size-only queries is not by itself an end-to-end startup timing result.

Fixed dynlink transaction status and commit replies advertise only their exact 32-byte body capacity. Peer CALL storage uses the validated common header plus the larger request/reply body: a BEGIN needs 72 bytes, while large record submissions and variable READ_PLAN replies retain their existing full capacity. Only that active transport prefix is cleared; the unused backing tail is neither transmitted nor parsed and is cleared before a later call makes it active. Fitting unwanted receipts are still retired before rejection, malformed or uncertain cleanup still tears down the process, and oversized kernel replies cannot install receipts. This is an implementation-cost reduction, not a measured end-to-end startup improvement.

The request-local object cache also retains the root's computed-and-verified SHA-256 alongside its immutable byte snapshot. A private typed root accessor supplies that exact pair for staged handoff, avoiding a second hash of the same bytes. The first verification of every selected object, live namespace metadata checks, exact-root endpoint reads, and each later launch's fresh validation are unchanged. A dependency failure prevents publication of the whole cache; no unverified manifest digest or cross-launch state can supply the handoff digest.

Clean-Room Policy

EriX follows a strict clean-room philosophy:

  • No external source code may be copied.
  • No external Rust crates are allowed.
  • No code generation tools that embed third-party code.
  • All code must be authored within the project.

Violations will result in rejection of the contribution.

License

All EriX repositories are licensed under the ISC License.

Development Model

EriX development is modular, deterministic, reproducible, authority-explicit, security-first, and self-hosting oriented.

This repository follows the project roadmap and the validation rules documented in its own roadmap.

Status

Implemented baseline:

  • no-std loaderd service entry and startup handshake
  • SERVICE_ROLE_LOADERD startup validation with no primary control endpoint
  • private launchd-only receiver at SLOT_LOADERD_LOCAL_LAUNCHD_ENDPOINT_V1 with RECV|GRANT; the public service dispatcher rejects PREPARE_PATH before any peer call
  • root PREPARE_SERVICE dispatch admits only canonical ttyd, powerboxd, and launchd paths at fixed root receipt slots 940 and 941; the optional dedicated deviced receiver admits only the canonical RTC provider and returns receipts into deviced's fixed scratch slots
  • private preparation admits exactly one executable-object SEND receipt from launchd's fixed source into loaderd's fixed staging slot. The accompanying canonical path, mode, size, and object digest are identity evidence, not authority; malformed or extra receipts are retired or fail-stop on uncertain cleanup
  • explicit send-only, read-only vfsd namespace session at SLOT_LOADERD_LOCAL_VFSD_NAMESPACE_ENDPOINT_V1; it cannot mount or mutate filesystems and cannot use handles created by another session
  • focused integration preflight that requires typed denial of mount control, directory enumeration, and write-open requests before loader discovery
  • explicit SEND|GRANT procd materialization peer at SLOT_LOADERD_LOCAL_PROCD_MATERIALIZATION_ENDPOINT_V1; it admits the sole staged install-grant receipt but cannot issue root orchestration or driver-lifecycle operations
  • required send-only dynlinkd startup peer at SLOT_LOADERD_LOCAL_DYNLINKD_ENDPOINT_V1
  • startup-private SHA-256 trust root and canonical VFS path for the selected runtime graph
  • DISCOVER_PATH IPC handling through lib-vfs client helpers
  • canonical path, file-type, empty-file, execute-bit, and stable metadata checks before reporting a discovered executable, without inheriting the launch transfer's storage capacity
  • LAUNCH_PATH dynamic-executable validation and materialization
  • successful path launch preserves procd's exact nonzero staged generation in its descriptive response, while failures return zero process identity, so later lifecycle queries never rely on a recycled numeric process ID alone
  • private PREPARE_PATH reuses the same validation, linking, and semantic materialization path. It reads the root executable only from the transferred exact-object endpoint with handle zero, verifies it against the signed graph, commits without starting, and returns both the verified executable-content digest and authenticated complete-graph digest with exactly one install grant from fixed loader-local slot 598 to fixed launchd-local slot 645. No caller-selected redirection is permitted, and the exact empty procd-local slot is preserved for final installation.
  • authenticated PREPARE_SERVICE uses the namespace-backed root selected by the signed runtime graph, declares the exact role and child endpoint at procd begin, and commits without starting. Success returns exactly the full staged process endpoint from fixed loader-local slot 600 and the install grant from slot 598 to the distinct destinations fixed for the authenticated root or deviced peer; failures return no authority or staged identity, and rejected or undelivered replies abort the exact generation and retire both local receipts.
  • target x86_64-unknown-none builds install a no-heap global allocator that always fails allocation; loader plans use caller-provided scratch buffers and a deployment-derived workspace patched into the final ELF
  • compact path-launch IPC with no unused argument, environment, source, profile, capability, peer, service, or link-policy selectors
  • malformed ELF and invalid segment-layout rejection before process mutation
  • native ET_DYN launch handling with complete dynamic tables, SysV hash, symbol and relocation metadata, canonical DT_SONAME and DT_RELACOUNT validation, and typed dynlinkd graph transactions before process handoff
  • arbitrary dependency cardinality and graph depth, constrained only by the exact signed runtime graph and deployment-provisioned workspace
  • deterministic rejection of static ET_EXEC sources
  • deterministic procd materialized process handoff after a valid dynamic plan, returning process id, entry point, and launch id on success; typed failures preserve the exact begin, segment, relocation, immediate-commit, staged-commit, or compensating-abort boundary
  • Materialization model: loaderd authenticates every selected closure object once into one launch-local, end-offset-indexed snapshot in its deployment-sized workspace. Catalogue construction, dynlink submission, relocation planning, and procd streaming reuse those immutable bytes instead of reopening the VFS object. procd owns frame allocation, segment copy, zero-fill, relocation mutation, permission sealing, address-space mapping, startup, and cleanup. Public launch commits and starts; private preparation commits the same complete image into an unstarted exact process generation.
  • Transfer ABI: lib-ipc defines begin, fragmented-segment, authoritative-relocation-batch, starting commit, staged commit, exact staged abort, and materialization abort bodies for the loaderd -> procd authority boundary
  • compensating materialization abort retains the exact transaction locally until procd returns correlated OK or authenticated NOT_FOUND; transport, malformed-reply, and retry results remain explicit cleanup obligations rather than being overwritten by the original launch error
  • Materialized launch: LAUNCH_PATH streams every selected object segment, forwards every dynlinkd response batch in one procd call while preserving every write exactly once, receives a procd launch id, and has no summary-only or combined-image compatibility path
  • Cleanup hardening clears executable scratch after source read, close, final-stat, ELF-plan, and process-handoff failures, and also drops the scratch buffer after successful handoff so executable bytes do not remain in loaderd state
  • Complete-graph unit coverage proves zero-dependency roots, graphs deeper than the retired policy ceiling, cycles, overlapping/W^X segments, fragmented identities, exact relocation streaming, zero-based shared-object placement, safe boundary-page sharing, and begin-failure cleanup
  • Dynamic cleanup clears the full dynamic materialization scratch region after object discovery, graph, object-load, relocation, or handoff failures, keeps dependency objects per-process-private in the current model, and preserves subsequent launch behavior after injected dynamic cleanup faults
  • Runtime peer calls use retained completion without a loader-local timeout or retry ceiling. Accepted calls keep their explicit message buffer until the authoritative reply arrives; no graph, object, dependency, name, table, or segment count is capped by caller policy. Required startup and service receives likewise have no loader-local tick deadline. Complete unexpected receipts are retired before rejection; malformed reply framing, uncertain cleanup, or terminal accepted-call state requests process teardown.
  • The target loop fairly scans the private launchd receiver before the public receiver. It preflights the exact-object staging slot as empty before each private receive and drops an accepted exact-object alias before sending any response. A staged reply is accepted only when correlation, process generation, materialization identity, receipt source/destination/type/rights, original procd reinstall destination, and the live fixed-slot capability all match. Rejected correlated success or failed delivery aborts the exact staged process and drops the grant; uncertain identity or cleanup fail-stops.
  • The dynamic service gate boots loaderd itself as a signed dynamic artifact. Target builds reserve a 512 KiB .bss.stack_reserve region, copy the complete incoming call envelope into transport-sized request scratch before nested VFS/dynlink/procd calls, and use a page-rounded .loader_workspace whose size is derived from the sum of every object in the largest packaged closure, one end-offset word per selected object, and the target Rust layouts embedded in the service ELF. Deployment writes that authenticated size into a volatile-read embedded word as well as expanding the mapped NOBITS range; an unpatched link-time placeholder is rejected.
  • Loader closure is validated by the focused loader-closure scenario group, which proves VFS policy rejection, malformed ELF rejection, true filesystem-byte execution, stale-source rejection, cleanup, and authority denial without granting extra loader authority
  • unit tests for startup, discovery, dispatch, and load-plan contracts

Dynamic Boot Artifact Evidence

The image build records loaderd as a dynamic boot artifact and packages the program loader service as an ELF64 x86_64 ET_DYN executable with .erix_dynlink metadata in the signed dynlink-store and mirrors it under /lib/erix/dynlink with its required shared objects.

Rootd stages dynlinkd before loaderd, passes the verified runtime-graph digest and canonical VFS path in loader-private startup data, and installs the public receiver plus all four required private peers. Dynamic packaging and filesystem mirror records are evidence and launch inputs only; they do not grant filesystem, loader, object-store, service-discovery, provider-bypass, block-device, or dynlinkd authority. loaderd receives only the documented startup endpoints, peers, and capabilities for its role.

Governance Principles

loaderd governance is scoped to executable source validation, load-plan construction, and exact started or staged materialization handoff.

The scoped governance rules are:

  • It must use explicit startup authority; no ambient VFS, provider, block, key, journal, or peer-service authority is allowed.
  • It must read executable bytes only through vfsd, so mounted-provider policy remains authoritative.
  • Private preparation must read root bytes only through the transferred exact VFS object; replacing its canonical namespace selector cannot redirect the bytes or change the returned content digest. The success response returns the selected root's stable dependency-manifest digest and the SHA-256 of the complete runtime graph actually authenticated. The invocation-local VFS object-identity digest remains request and receipt evidence only. The runtime graph and shared dependencies continue to use the attenuated read-only namespace session.
  • It must fail closed before process mutation unless dynamic metadata is accepted by dynlinkd when required and procd accepts the typed handoff.
  • Filesystem-backed execution must transfer validated segment bytes and authoritative relocation batches explicitly to procd; no summary-only or raw combined-image handoff exists.
  • Git dependencies use full 40-character commit rev pins. Integration source revisions and CI caches preserve those identities, with no branch-head or synthetic-commit substitution.

Policy Boundaries

  • loaderd does not own process lifecycle; procd remains responsible for process objects, address-space creation, startup, event tracking, and cleanup.
  • loaderd does not own address-space or paging policy; vspaced and pagerd retain those responsibilities.
  • loaderd does not bypass vfsd or private filesystem providers.
  • loaderd does not receive memory, VSPACE, or general process-control authority. Its sole transient install grant arrives in fixed slot 598 after exact procd reply validation and is moved only to launchd's fixed slot 645; it cannot be retained, redirected, or used to start the process.

Contact

Development occurs in EriX organization and discussions happen in issues and design documents.

Discussions should occur via issues or design documents.

No decisions are considered valid without documented rationale.

You can also reach out to maintainers via email: admin@erikinkinen.fi.