Skip to content

Port the managed agent to Rust - #531

Draft
Pedro Henrique Penna (ppenna) wants to merge 4 commits into
devfrom
agents/fix-issue-358-managed-agent
Draft

Pedro Henrique Penna (ppenna) wants to merge 4 commits into
devfrom
agents/fix-issue-358-managed-agent

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add the guest-side NVXS attach, ACK, and CREDIT records, the 1 MiB receive
    window, current feature masks, and the nvx_maps=COUNT parser
  • add byte-oriented guest EXEC and MAPS decoders without changing the existing
    UTF-8 host API
  • port nvx-managed-agent to a static Rust binary, including its session,
    dispatch, execution, containment, mapping, helper, launch, TTY, and
    diagnostic behavior
  • put every OS-facing unsafe operation behind documented safe
    nvx::guest::sys APIs, with OwnedFd-backed RAII resources
  • port the C-driven tests and connect the host ControlSession through a
    credit-accounting broker shim to the real agent and exec engine
  • build and test the agent through nvx.py with the other Rust guest binaries

Part of #358 (PR 2 of 5). Builds on #528, which has merged into dev.

The guests still ship guest/common/nvx-managed-agent.c. PR 5 will switch the
guest builds and remove the C programs and their old test harnesses.

PR #528 conventions

  • Standard-library agent modules deny unsafe code. Safe wrappers in
    nvx::guest::sys use nix 0.31.3 for descriptors, pipes, poll, termios,
    mounts, memfds and seals, identities, signals, kill, and wait operations
    where nix preserves the syscall contract. Focused libc and architecture FFI
    remain only in sys.
  • File descriptors, pipes, the sealed configuration memfd, and mappings are
    RAII-owned. Explicit consuming close/unmap paths retain C cleanup errors;
    Drop handles paths where C ignored cleanup. Raw descriptor integers never
    leave sys.
  • The agent and its helper have distinct exit-status enums. Mapping, launch,
    environment, process, and protocol failures use typed results and enums
    internally.
  • Every public and private module, type, field, variant, constant, static, and
    function is documented under the crate-wide
    clippy::missing_docs_in_private_items lint.
  • Numeric IDs use the shared libc strtoull boundary. Helper environments use
    a safe ownership-transferring putenv wrapper, retaining the C contract.
  • GuestRustBuildConstants.HOSTED_BINARIES includes nvx-managed-agent, so
    nvx.py build-guest-binaries and test-guest-binaries build, inspect,
    manifest, and test it. A manifest-validated Port the small guest tools to Rust #528 six-binary output upgrades
    in place; unrelated inventories, files, symlinks, and digest mismatches are
    still refused.

Unsafe-count methodology is the number of unsafe { blocks and unsafe fn
definitions in PR 2's agent, protocol, binary, and added system wrappers.
Before the rework: 79 blocks / 2 functions, all outside sys. After: 33 / 2,
all inside sys, and 0 / 0 outside it. The complete guest::sys layer,
including #528, now has 48 / 2.

Fork boundary and parity

The safe fork entry point receives owned C strings, owned descriptors, and
precomputed descriptor actions. It constructs the complete argv, envp,
physical-PWD storage, and pointer vectors before fork. The child allocates
nothing, performs no Rust cleanup, runs only direct async-signal-safe
Linux/musl operations, and terminates through execve, execv, or _exit.

  • Direct launch preserves the C order: clear supplementary groups, take the
    workload GID and UID, enter the cwd, regain root, join nvx-exec, then run
    setpriv with --reuid, --regid, --clear-groups,
    --no-new-privs, and all three capability-clearing options.
  • The current boot contract remains one decimal nvx_maps=COUNT token from 1
    through 4096. Retired nvx_map= tokens remain ignored.
  • Linux argv, environment, and path fields remain arbitrary non-NUL bytes.
    Dedicated guest decoders retain the C agent's 4096-byte cwd bound.
  • Canonical cwd names retain the supplied spelling. Noncanonical names fill a
    preallocated physical-PWD slot after the child enters the directory under
    workload credentials. Failures remain cwd-failed with the original errno.
  • Diagnostics and wire bytes remain unchanged, apart from the already
    documented musl strerror wording.

Security review

The earlier independent review of the original port found no vulnerabilities.
Its one non-security cwd/PWD parity finding was fixed before this rework.

The post-refactor self-review found no regression:

  • privilege-drop ordering and the setpriv chain are unchanged
  • every pipe, control, cgroup, barrier, diagnostic, and mapping descriptor is
    close-on-exec; only the sealed helper configuration deliberately survives
    exec, and the helper closes it
  • file mapping targets still use
    O_NOFOLLOW|O_CREAT|O_EXCL|O_CLOEXEC
  • bind remounts still use
    MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NODEV, plus MS_RDONLY when requested;
    the hostfs parent remains root:root mode 0700
  • inherited SIGPIPE is restored at startup; barrier SIGPIPE changes have an
    explicit error path and best-effort RAII restoration
  • process-group and cgroup kills, strict pids.current, orphan reaping, and
    containment-failure latching are unchanged; raw wait-status decoding keeps
    every Linux real-time signal as a terminal workload outcome
  • every host-controlled count, length, and offset is checked before allocation
    or indexing; no untrusted-input path can panic

PID-1-style reaping tests run in isolated test-harness subprocesses, so their
intentional waitpid(-1) behavior cannot collect children owned by parallel
host tests.

Validation

Windows

  • python scripts\nvx.py verify
  • complete check-nvx-rs: workspace fmt, both Clippy modes, default and
    all-feature tests, synthetic bundle, rustdoc with warnings denied, Rust 1.89
    MSRV, aarch64-apple-darwin, and musl dev/guest-profile Clippy
  • complete check-aci-edge-sandboxes
  • affected guest-binary/CI tests, Ruff check/format, and Linux+Windows Pyright

WSL Linux

  • complete GNU and musl check-nvx-rs commands and complete dependent ACI
    crate checks
  • three consecutive all-feature workspace suites after isolating process tests
  • two fresh nvx.py build-guest-binaries builds are byte-identical; all 22
    prebuilt-binary tests pass
  • a real Port the small guest tools to Rust #528 six-binary output rebuilds safely in place to the seven-binary
    inventory, then passes all prebuilt tests
  • final source digest:
    8425dbbfea5d10453b586bda140233066ec2bebd05673aeaf65437f411c4f2dc

Differential

On both prometheus32 and prometheus30:

  • C versus old Rust, C versus reworked Rust, and old versus reworked Rust each
    match all 3,600 deterministic cases: 1,200 EXEC, 1,200 MAPS, and 1,200
    nvx_maps command lines
  • accepted counts remain 148, 144, and 1,081 respectively
  • old/new loopback protocol transcripts canonicalize to the same 42 events and
    2,433 bytes, SHA-256
    8a90e0da18f2f64729edc009220bfaf3787cd11fafc5130ecde873697fe45576;
    canonicalization removes random request IDs, pipe chunk boundaries, and the
    output-limit case's permitted bounded zero-prefix length

In VM

Fresh final-head binaries were overlaid into copies of the current Alpine and
Ubuntu initramfses on prometheus32 (KVM/baremetal) and prometheus30
(MSHV/baremetal).

  • Alpine: managed-exec-config, sandbox-lifecycle, sandbox-blocks, and
    scratch-snapshot
  • Ubuntu: managed-lifecycle and filesystem-shares
  • in-guest agent identity and cwd parity:
    • KVM SHA-256 d57f7582c7857337d93ce77d37f21099aaedaf16fe3ad8e7d61ebc61d9a799c0
    • MSHV SHA-256 ada79dc0a747d903d8ef6da675863458fa38e2b0bcf6b10101f6c90e00f25509
  • KVM test-aci-edge-sandboxes: 18/18 pass
  • MSHV: 17/18 pass; the new host-loopback test times out waiting for port
    publication on this host and fails identically with the untouched baseline
    initramfs. The agent-relevant tests and the prior 17-test suite pass.

The final Rust agent is a static PIE: 459,232 bytes on prometheus32 and
459,296 bytes on prometheus30, about 35.4% smaller than the 711,400-byte C
agent.

Both hosts' original initramfs files were restored byte-for-byte after every
run. Scratch/state directories were removed, validation worktrees and
submodules are clean, and no OpenVMM process remains.

A second independent review of the reworked agent found no regressions against the C implementation and identified two internal API soundness gaps in the new system layer, which are now fixed.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 19:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Data-record failures no longer preserve the C agent’s diagnostic stages and status details.

1 open finding
What changed in this PR

Ports the managed guest agent to Rust while preserving the existing protocol, execution, containment, mapping, and diagnostic contracts. Guest images continue using the C agent until a later build-switch PR.

Changes:

  • Adds byte-oriented guest protocol decoding and managed-agent session handling.
  • Implements execution, cgroup containment, mappings, launch helpers, and loopback tests.
  • Adds static musl guest binaries, ELF validation, CI coverage, and documentation.
File Description
.github/​actions/​check-nvx-rs/​action.yml Builds and validates musl guest binaries.
doc/​ci.md Documents guest-binary CI checks.
doc/​contribute.md Updates Rust validation guidance.
doc/​project-structure.md Documents guest modules and binaries.
doc/​usage.md Documents check-static-elf.
nvx-rs/​Cargo.toml Defines guest feature, binaries, and profile.
nvx-rs/​README.md Documents guest binaries and agent architecture.
nvx-rs/​src/​lib.rs Exposes Linux guest modules.
nvx-rs/​src/​bin/​nvx-console-pending.rs Adds console-pending entry point.
nvx-rs/​src/​bin/​nvx-device-io.rs Adds freestanding device benchmark entry point.
nvx-rs/​src/​bin/​nvx-exit.rs Adds freestanding VM-exit entry point.
nvx-rs/​src/​bin/​nvx-managed-agent.rs Adds managed-agent entry point.
nvx-rs/​src/​bin/​nvx-mmio-write.rs Adds MMIO writer entry point.
nvx-rs/​src/​bin/​nvx-port-io.rs Adds port-I/O entry point.
nvx-rs/​src/​bin/​nvx-reseed.rs Adds reseed entry point.
nvx-rs/​src/​guest/​agent/​cgroup.rs Implements workload cgroup containment.
nvx-rs/​src/​guest/​agent/​diagnostics.rs Implements agent diagnostics and descriptor I/O.
nvx-rs/​src/​guest/​agent/​dispatch.rs Dispatches control requests.
nvx-rs/​src/​guest/​agent/​exec.rs Supervises workload execution and output.
nvx-rs/​src/​guest/​agent/​exec_config.rs Encodes sealed helper configuration.
nvx-rs/​src/​guest/​agent/​helper.rs Applies helper environment and working directory.
nvx-rs/​src/​guest/​agent/​launch.rs Implements fork and launch preparation.
nvx-rs/​src/​guest/​agent/​loopback.rs Tests real host-to-agent control flow.
nvx-rs/​src/​guest/​agent/​mappings.rs Applies announced host mappings.
nvx-rs/​src/​guest/​agent/​mod.rs Defines managed-agent modes and lifecycle.
nvx-rs/​src/​guest/​agent/​seam.rs Provides production and test system seams.
nvx-rs/​src/​guest/​agent/​session.rs Implements guest protocol sequencing and credit.
nvx-rs/​src/​guest/​agent/​tty.rs Opens and configures the control TTY.
nvx-rs/​src/​guest/​console_pending.rs Ports pending-console inspection.
nvx-rs/​src/​guest/​freestanding/​args.rs Parses kernel-provided arguments.
nvx-rs/​src/​guest/​freestanding/​device_io.rs Ports the freestanding I/O benchmark.
nvx-rs/​src/​guest/​freestanding/​exit.rs Ports the freestanding exit utility.
nvx-rs/​src/​guest/​freestanding/​hosted.rs Provides hosted test entry points.
nvx-rs/​src/​guest/​freestanding/​mem.rs Supplies freestanding memory routines.
nvx-rs/​src/​guest/​freestanding/​mod.rs Organizes freestanding components.
nvx-rs/​src/​guest/​freestanding/​start.rs Supplies _start and panic handling.
nvx-rs/​src/​guest/​freestanding/​syscall.rs Wraps raw x86-64 syscalls.
nvx-rs/​src/​guest/​mmio_write.rs Ports MMIO writing.
nvx-rs/​src/​guest/​mod.rs Organizes guest programs.
nvx-rs/​src/​guest/​parse.rs Reimplements C-compatible numeric parsing.
nvx-rs/​src/​guest/​port_io.rs Ports port and generation-ID operations.
nvx-rs/​src/​guest/​reseed.rs Ports entropy reseeding.
nvx-rs/​src/​guest/​sys.rs Preserves shared C runtime behavior.
nvx-rs/​src/​protocol/​boot.rs Parses nvx_maps=COUNT.
nvx-rs/​src/​protocol/​exec.rs Adds byte-oriented guest EXEC decoding.
nvx-rs/​src/​protocol/​features.rs Defines direct and sandbox feature masks.
nvx-rs/​src/​protocol/​maps.rs Adds byte-oriented guest MAPS decoding.
nvx-rs/​src/​protocol/​mod.rs Exports new guest protocol APIs.
nvx-rs/​src/​protocol/​outer.rs Adds guest attach, ACK, and CREDIT records.
nvx-rs/​src/​protocol/​tests.rs Tests guest protocol parity and boundaries.
nvx-rs/​tests/​guest_tools.rs Exercises built guest binaries.
scripts/​nvx.py Adds the static-ELF command.
scripts/​nvx_tools/​elf.py Implements ELF linkage inspection.
scripts/​test_nvx_tools.py Tests CI coverage and ELF validation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread nvx-rs/src/guest/agent/dispatch.rs Outdated
Copilot AI balanced review requested due to automatic review settings October 13, 2026 19:49
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 9f13734 to 7548a55 Compare October 13, 2026 19:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The extensive unsafe fork/exec, protocol, containment, and privileged filesystem changes require final human review despite strong parity coverage.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged launch and containment port needs final human review, and unresolved correctness issues remain.

2 open findings

🧠 Review effort: Balanced

Comment thread nvx-rs/src/guest/sys/fd.rs Outdated
Comment thread nvx-rs/src/guest/managed_agent/cgroup.rs
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 5378207 to 54f904a Compare October 14, 2026 19:37
Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:37
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 54f904a to f4f6b80 Compare October 14, 2026 19:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unresolved correctness issues and the privileged fork/exec boundary require fixes and final human review.

2 open findings
2 resolved since last review

🧠 Review effort: Balanced

Comment thread nvx-rs/src/guest/sys/process.rs Outdated
Comment thread nvx-rs/src/guest/agent/exec_config.rs Outdated
Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privilege-sensitive launch, containment, and system-interface port warrants final human review.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from f4f6b80 to 8d0a02c Compare October 14, 2026 19:56
Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged agent port warrants final human review, and compatibility with existing build-output directories remains unresolved.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread scripts/nvx_tools/build_constants.py
Copilot AI balanced review requested due to automatic review settings October 14, 2026 20:13
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 8d0a02c to 0ed5ad7 Compare October 14, 2026 20:13
Copilot AI balanced review requested due to automatic review settings October 15, 2026 01:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged process-launch, identity-transition, containment, and mount boundaries warrant final human review.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 15, 2026 04:03
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 03fff78 to ad98df0 Compare October 15, 2026 04:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged agent port needs human review of its process-safety boundaries, including the unresolved environment-mutation contract.

1 open finding

🧠 Review effort: Balanced

Comment thread nvx-rs/src/guest/agent/mod.rs Outdated
Copilot AI balanced review requested due to automatic review settings October 15, 2026 04:24
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from ad98df0 to c46d300 Compare October 15, 2026 04:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged agent port’s process-launch, descriptor-ownership, and containment boundaries warrant final human review.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The privileged agent rewrite, fork/exec boundary, and containment behavior warrant final human review.

0 open findings

🧠 Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) changed the base branch from dev to agents/fix-issue-358-validate-changes-ci October 15, 2026 17:38
Base automatically changed from agents/fix-issue-358-validate-changes-ci to dev October 15, 2026 18:06
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as draft October 15, 2026 18:17
Add the guest attachment, acknowledgement, and credit records and
window payloads. Parse the current nvx_maps count contract and expose
the feature sets announced by each agent mode.

Keep byte-oriented EXEC and MAPS decoders for the Linux guest beside the
existing UTF-8 host decoders, including the C guest's 4096-byte cwd
bound. Pin the new records and parser with golden and round-trip tests.

Part of #358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port the managed agent as a static Rust guest binary while preserving
its control session, execution, cgroup, host-mapping, sealed
configuration, helper, and diagnostic contracts.

Follow the guest programs' conventions. The agent and its execution
helper fail with the shared CommandError and exit with the new shared
ExitStatus::AgentFailure, 125. Errors are nix's Errno, whose
descriptions the diagnostics print, and the safe nvx::guest::sys
wrappers make the system calls through nix. Only the forked child, the
raw wait-status decoding, which keeps real-time signals, and the
duplication of the inherited descriptor reach libc, through nix's
re-export. The standard library parses the decimal identities and
descriptor, and no resource is dropped by hand.

Prepare argv, envp, physical-PWD storage, and descriptor actions before
fork. The child allocates nothing and terminates through exec or _exit.
The helper builds the workload's environment in memory and passes it to
execve, so no process changes its own environment and the binary needs
no unsafe entry point. Keep the privileged seam for production wrappers
and failure-injecting tests, and cover the complete path with the
host-to-agent loopback.

Part of #358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise the managed agent helper as a real process, including exact and
layered environments, cwd handling, malformed configuration, the
internal descriptor lifetime, and command-line failures. The tests can
use either the Cargo test binary or the prebuilt guest-profile binary.

Part of #358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add nvx-managed-agent to the Rust guest-binary inventory. The existing
nvx.py build-guest-binaries and test-guest-binaries commands now build
its static musl executable in the same fresh, reproducible Cargo build
as the other guest programs, check its ELF form, include its digest in
the manifest, and run its prebuilt helper integration tests.

An output whose manifest validates the previous six-binary inventory
upgrades in place; unrelated inventories, files, symlinks, and digest
mismatches are still refused. The command documentation now counts
seven binaries.

Part of #358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 18:32
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-358-managed-agent branch from 99b6bfb to 61d24f1 Compare October 15, 2026 18:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The extensive privileged fork/exec, containment, and filesystem-mapping port warrants final human safety review.

0 open findings

🧠 Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants