Repository navigation
nvx-rs: factor minimal guest rootfs and descriptor-rooted host mapping #539
Copy link
Copy link
Closed as not planned
Closed as not planned
Copy link
Description
Parent: #535. Phase 2 (guest foundation). Build on #358, the open guest-tool PR #528 and managed-agent PR #531. #359 ports a host launch profile; it does not port the guest rootfs shell in guest/common/nvx-init-agent.
Goal
Extract a small, transport-neutral nvx::guest image/root/process substrate from the Rust guest port and the relevant shell init steps. A standalone image-backed sandbox and a richer caller should assemble the same root and apply the same ordered host-map semantics without pulling a full application RPC stack into a static guest library.
Mechanics
- Accept ordered read-only layers supplied by a profile-specific image resolver: GPT p2+ ext4 for one profile, EROFS layers for another. Specify each layer's cold-plugged device role and source-bound fixture, including the GPT multi-partition case. For this image-backed profile, one read-only GPT disk supplies every p2+ lower: direct
distroon cold boot or boundimage0after template restore. Its partitions are not additional image slots; an EROFS-layer profile may use other resolver/device arrangements without adding slots to this one. Assemble an overlay withUpper::TmpfsorUpper::Ext4Scratch; do not force a scratch disk on the RAM-upper path or change the existing shell profile before its replacement is accepted. - Decode and validate the current
nvx_maps=COUNT+ ordered APP_MAPS table from Port the managed agent to Rust #531, not the obsoletenvx_map=wording in nvx-rs: replace the guest C programs with Rust binaries #358. Separate validatedHostMap { source, target, read_only }semantics from literal NVXC frames: another authenticated adapter may carry the same table in its own framing. - Apply mappings by opening source and overlay root descriptors, resolving beneath them without symlink/magic-link escapes, creating targets only on the owned overlay, and applying RO/nosuid/nodev before attaching each bind. Reserve runtime-owned targets consistently; Track sandbox live share bugs: the time ABI bind hides or breaks shares at /run targets #486 tracks an existing
/run/nvx/timeshare collision, so this core must not repeat it. - New activation profiles accept zero mappings (no batches/dormant export). Public NVXC v1 retains its existing 1..4096 boot-count and nonempty MAPS grammar for any explicitly supported old bundle. Nonzero tables require exact order/count before Ready/exec; partial side effects fail closed.
- Expose basic root-entry/spawn/cancel/reap and whole-workload settlement hooks under a caller-fixed identity/isolation contract. A terminal exec must not leave a descendant or inherited pipe writer before readmission, even after cancellation/timeout/reconnect; failure blocks further exec or tears down the verified VM. Preserve caller-fixed non-root/cgroup/
setprivand bounded-capability-root policies without a user-facing isolation-profile selector or protobuf, ttrpc, OCI, runc, or host Tokio in the small guest feature. A root caller must not reach the raw export, control device or configuration region or change a read-only host mapping.
Acceptance
- Static guest build and size/boot deltas are recorded against nvx-rs: replace the guest C programs with Rust binaries #358's guest profile; relevant rootfs behavior previously in shell is represented in Rust.
- Layer/upper combinations and cold-plugged device roles work without adding devices at runtime. A synthetic role fixture exercises the same GPT root assembly from cold
distroand from a boundimage0; nvx-rs: prove cross-image platform restore with pre-ready activation RPC #542, not this foundation, proves real post-restore media discovery and the one-slot VMM ABI. - Tests cover zero/one/max ordered mappings, cross-volume children, nested targets, RO/RW/denied objects, symlinks and mount traversal, reserved targets (Track sandbox live share bugs: the time ABI bind hides or breaks shares at /run targets #486), and fatal partial-map failures.
- A static guest builds with
--no-default-features --features guestwithout host/Tokio/protobuf/ttrpc/OCI/runc dependencies. Measure guest binary/initramfs size and image-root assembly p50/p95 against the source-matched baseline and agree a regression budget before accepting new work; zero maps require no virtio-fs mount or per-map path walk. - The same root/mapping semantic vectors run through two small independent test adapters with different application framing; real standalone and downstream adapter adoption is accepted in nvx-rs: provide a standalone image-backed sandbox cold-start path #541/nvx-rs: prove shared host and guest mechanics through two independent adapters #545. The guest core requires neither framing and the adapters do not reimplement root or bind safety. Fixtures exercise both upper modes and prove inaccessible raw exports and non-bypassable nested read-only mappings for the root caller.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
ppenna commentedon Oct 16, 2026
Five suggestions from the #531 review belong here. #531 keeps a faithful port of the C agent, so they wait until the #358 series is done:
SpawnRequestcarriesAgentConfig, the agent's argv strings, andGuestExecPayload, the decoded NVXC frame. Convert them once at dispatch into a neutral exec spec (argv, environment, inherit flag, cwd, timeout) and an explicit identity, so that the root-entry and spawn hooks take input that another adapter can build. UID and GID validation can stay at launch time to keep today's failure stage.cgroup::reapandcgroup::verify_emptycallwaitpid(-1, WNOHANG)from the execution path, as the C agent does, so they collect any child of the process. One agent-owned reaper that keeps PID 1's orphan reaping and records exit statuses by PID, which the execution core asks for its own child's status, lets the core run in a process with other children (nvx-rs: prove shared host and guest mechanics through two independent adapters #545).cgroup::prepare,has_tasks,kill, andlaunch.rseach rebuild thenvx-execpath, so only the direct root's fixed cgroup gets cgroup-level settlement, and in sandbox modecgroup::terminatekills only the process group. AnExecCgroup { dir }withprepare,has_tasks,kill,settle, andprocsserves whole-workload settlement in a layered root and one cgroup per execution for a concurrent adapter (nvx-rs: prove shared host and guest mechanics through two independent adapters #545). NVXC v1 still createsnvx-execon everyEXEC.apply_maps(system, export_root, target_root, first, maps). The NVXC loop keepsHostMappingsand its sequence rules and passes/, so paths stay byte-identical until the descriptor-rooted walk replaces the inside.make_directoriesand bothmountcalls follow symlinks, and only the file target refuses one, withO_NOFOLLOWon its last component. The C agent does the same. For example, withworkread-only,work/outread-write, andwork/out/deps/cacheread-only, a writer that replaceswork/out/depswith a symlink to/varafter the host'sExportPlan::verifymakes the bind and its remount land on the guest's/var/cache. The descriptor-rooted walk (openat2withRESOLVE_BENEATH | RESOLVE_NO_SYMLINKS, thenopen_tree,mount_setattr, andmove_mount) must refuse it, and a regression test should cover this case.The first four keep NVXC v1's wire bytes, limits, exit statuses, and diagnostics. #358 checks in the C agent's parity fixtures before it removes the C agent, and those fixtures can prove it.
esaurez commentedon Oct 16, 2026
From the #550 review (the Rust time tools), deferred because #550 keeps a faithful port. For this core:
Expose the time steps as library calls. In Port the time tools to Rust #550,
nvx::guest::timeandnvx::guest::reseedexport onlymain(), and the validated restore packet leavesrepair_restoreonly as a text line and an entropy file. A Rust PID 1 must therefore startnvx-time capture,nvx-reseedandnvx-time restore-finishand parse their output (#358 asked PR 4 for these calls). Keep the CLI as a thin wrapper, with today's bytes and statuses, over calls such as:time::boot();time::capture(request, previous_id), which returns the validatedRestorePacketafter the clock step, or that nothing was restored;time::restore_finish(acknowledge, new_cpus);reseed::reseed(seed, previous_id).Failures stay fatal through a helper that the CLI also uses. The time daemon stays a separate process, because C10 requires its name to be
nvx-timeand its PID to be greater than 1.Scope port access to each transaction.
RealOpsrequestsPortAccessfor the whole command, and the thread's I/O bitmap passes toforkchildren and survivesexecve, whatever their capabilities. An in-process caller must take and drop it for each port transaction, never while it starts a workload. Add a test that a workload can neither read the byte channel nor write the snapshot or shutdown port.With both, neither adapter needs
nvx-snapshotor helper processes on a readiness path (#542, #545).esaurez commentedon Oct 20, 2026
Replaced by the rewritten #535. Neutral launch, a single reaper, an exec cgroup, explicit roots, time as library calls and per-transaction ports move to #619 (A1-guest). The minimal layered root, with the Rust PID 1, descriptor-rooted mappings and ordered maps, moves to #621 (A3). Closing in favor of those issues.