- Rust 99.9%
- Linker Script 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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: |
||
| .github | ||
| src | ||
| .editorconfig | ||
| .gitignore | ||
| .markdownlint-cli2.yaml | ||
| ARCHITECTURE.md | ||
| Cargo.toml | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| linker.ld | ||
| README.md | ||
| ROADMAP.md | ||
| rustfmt.toml | ||
| SECURITY.md | ||
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/dynlinkdpeers - resolve canonical absolute UTF-8 VFS paths through public
vfsdcalls - 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_EXECversus dynamicET_DYNexecutable 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
procdthrough 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
procdbegin/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, orlaunchd, while deviced may name onlydrv-rtc-cmos; return the paired staged endpoint and install grant only at that peer's fixed receipt slots - accept exactly one
SENDexecutable-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
loaderdservice entry and startup handshake SERVICE_ROLE_LOADERDstartup validation with no primary control endpoint- private launchd-only receiver at
SLOT_LOADERD_LOCAL_LAUNCHD_ENDPOINT_V1withRECV|GRANT; the public service dispatcher rejectsPREPARE_PATHbefore any peer call - root
PREPARE_SERVICEdispatch 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
SENDreceipt 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
vfsdnamespace session atSLOT_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|GRANTprocd materialization peer atSLOT_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
dynlinkdstartup peer atSLOT_LOADERD_LOCAL_DYNLINKD_ENDPOINT_V1 - startup-private SHA-256 trust root and canonical VFS path for the selected runtime graph
DISCOVER_PATHIPC handling throughlib-vfsclient 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_PATHdynamic-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_PATHreuses 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_SERVICEuses 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-nonebuilds 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_DYNlaunch handling with complete dynamic tables, SysV hash, symbol and relocation metadata, canonicalDT_SONAMEandDT_RELACOUNTvalidation, and typeddynlinkdgraph 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_EXECsources - deterministic
procdmaterialized 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:
loaderdauthenticates 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.procdowns 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-ipcdefines begin, fragmented-segment, authoritative-relocation-batch, starting commit, staged commit, exact staged abort, and materialization abort bodies for theloaderd -> procdauthority boundary - compensating materialization abort retains the exact transaction locally
until procd returns correlated
OKor authenticatedNOT_FOUND; transport, malformed-reply, and retry results remain explicit cleanup obligations rather than being overwritten by the original launch error - Materialized launch:
LAUNCH_PATHstreams every selected object segment, forwards everydynlinkdresponse batch in oneprocdcall while preserving every write exactly once, receives aprocdlaunch 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
loaderdstate - 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
loaderditself as a signed dynamic artifact. Target builds reserve a 512 KiB.bss.stack_reserveregion, copy the complete incoming call envelope into transport-sized request scratch before nested VFS/dynlink/procd calls, and use a page-rounded.loader_workspacewhose 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-closurescenario 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
dynlinkdwhen required andprocdaccepts 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
revpins. Integration source revisions and CI caches preserve those identities, with no branch-head or synthetic-commit substitution.
Policy Boundaries
loaderddoes not own process lifecycle;procdremains responsible for process objects, address-space creation, startup, event tracking, and cleanup.loaderddoes not own address-space or paging policy;vspacedandpagerdretain those responsibilities.loaderddoes not bypassvfsdor private filesystem providers.loaderddoes 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.