Skip to content

cli: check the command line that a sandbox launches (SL6.0f) - #1526

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl6-0f-cli-command-line
Nov 12, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl6-0f-cli-command-line

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1120: SL6.0f, the CLI checks the command line that it launches. It prepares SL6.3 (#1122), which then deletes the old config types whole and stays leaving-code-only (decisions D2 and D3 on #1122).

What and why

sandbox start and sandbox run refused a user's --cmdline through the old launch profile of host::sandbox: check_command_line, in sandbox/lifecycle/start.rs since SL3.10d (#1503), built a SandboxLaunch of the record with a SandboxUpper and SandboxSettings, bound its plan to the distro binding, and rendered the plan's kernel command line. That is not the line that a start or a run launches: it put the user's text first, then nvx_sandbox=1, the root's tokens, nvx_entrypoint= and nvx_hostname=, and it left out the view's and the managed lifecycle's tokens. nvx-rs run checked its shares against the kernel's budget with host::sandbox::SANDBOX_COMMAND_LINE_MAX_SIZE and mount::export_command_line_fragment. These were the CLI's last uses of the old launch, which #1503 left for later slices.

Now both check the line that they launch, the one that Config::vm_config's VmConfig::kernel_command_line renders: the root's and the view's tokens, nvx_maps=, nvx_sandbox=1 nvx_mode=managed nvx_identity=UID:GID, then the user's text, as given.

  • check_command_line(config, vm) refuses, in this order and with unsupported:
    • a NUL in the user's text, and a token of it that the sandbox profile owns, nvx_* or nvx-*, or that --mount owns, virtfs_* or virtfs-*, as Linux (cmdline::parameters) or the guest's init (cmdline::init_tokens) reads it, with the old messages;
    • a launched line that, with the bootstrap tokens that OpenVMM appends for the shares and a NUL, exceeds host::config::COMMAND_LINE_BUDGET, the same 1024 bytes as SANDBOX_COMMAND_LINE_MAX_SIZE (2048 − 1024), with the old message;
    • a launched line whose root tokens RootLayout::parse refuses, such as one with a virtio-mmio device at the scratch disk's base beside a RAM upper, with the old message;
    • a launched line that Linux does not read to its end, with a -- or an unclosed quote as Linux reads them, which would hide the tokens that OpenVMM appends after it. The check appends nvx_appended, a parameter that the user's text cannot name, since its nvx_* tokens are refused first, and requires Linux to read it last.
  • export_tokens, in cli/sandbox/mod.rs, renders the bootstrap tokens of an export at /run/nvx/hostfs/root, as export_command_line_fragment rendered them, for both checks. cli/run.rs checks its budget with it and COMMAND_LINE_BUDGET, with the same arithmetic and message.
  • Config::hostname loses its last caller, the old check, and goes.

What changes for a user, on purpose:

  • The budget counts the launched line. For the default hostname it is 113 to 116 bytes longer than the old plan's line, and longer for a longer hostname, which the view writes in hexadecimal, so --cmdline has that much less room before sandbox start or sandbox run refuses it. The old check let such a line exceed NVX's 1024 bytes of the kernel's command line.
  • The hiding refusal names OpenVMM's tokens. The user's text now follows the root's tokens, which it can no longer hide, but precedes the tokens that OpenVMM appends, the virtio-mmio devices of the root's disks among them. The refusal says sandbox kernel command line hides the tokens that OpenVMM appends to it, where it said ... hides the boot tokens of its root. It also catches a -- after a byte that only Linux takes for whitespace, such as the 0xa0 that ends à, which VmConfig's control-console check, splitting at ASCII whitespace, misses. nvx-rs run, which this does not change, still accepts such a line.
  • The user's text is checked as given, not trimmed, since the launched line holds it as given.
  • A start builds its VmConfig before it checks the command line, so a record that Config::vm_config refuses, which only an edited config.json can be, reports that before a missing artifact.

Closed double quotes stay refused as before: by VmConfig::check_command_line, which sandbox run already called before its launch, and by a start when it creates its VM.

Docs: doc/usage.md's --cmdline row and doc/run.md's single-workload sandbox section say how --cmdline is checked. They still said that quoted values keep their whitespace and that a -- or a quote hides the root's tokens.

Size. 86 core lines, 4 counted files: sandbox/lifecycle/start.rs 65, sandbox/mod.rs 13, run.rs 8, and sandbox/state.rs 0, which only loses Config::hostname. The estimate was 50–80 lines in 2–3 files, with a stop at 160 lines or 5 files. The public API, BOOT_CONTRACT_VERSION and CONTROL_CONTRACT_REVISION do not change.

Stack. Based on feature/nvx-refactor ff15032cdc5. One commit, b06d52fa085. SL6.3 (#1530) is stacked on this branch.

Case matrix. No row of doc/acceptance-case-matrix.md lists a test that this changes or names one as coverage, and the new test belongs to none of its inventoried sets.

Validation

Check Result
Windows, at 93469a4aca7, this commit before its rebase onto ff15032cdc5: cargo fmt --check; Clippy with -D warnings for every feature set of check-nvx-rs, for Windows and x86_64-unknown-linux-gnu, and cargo check --all-targets --all-features --target aarch64-apple-darwin; cargo doc --no-deps --all-features with -D warnings; public_api.py; check_test_layout.py Passed
Windows, at 93469a4aca7: cargo test --all-features, cargo test, cargo test --lib --no-default-features Passed: 17 suites, 1353 tests (all features), with lib 1169 and cli 111; 14 suites, 921 tests (default), with lib 853; lib 246 (no default features)
Linux (nvx-kvm-test, Azure Linux 3, kernel 6.6, KVM, virtual machine), at 93469a4aca7: cargo test --all-features --lib -- host::cli, cargo test --all-features --test cli; nvx.py test-microvm --backend kvm with sandbox-lifecycle, sandbox-ram-upper and managed-exec-config Passed: lib 226, cli 120; the scenarios with nvx-rs built at that commit and the artifacts of the last row
Linux (nvx-mshv-test, Azure Linux 3, kernel 6.18, MSHV, virtual machine), at SL6.3's head 604d8dacccb, which carries this commit unchanged: cargo test --all-features, cargo test, Clippy --all-targets --all-features; nvx.py test-microvm --backend mshv with sandbox-lifecycle, sandbox-ram-upper, managed-exec-config, workload-identity and managed-lifecycle Passed: 95 suites, 2292 tests; 13 suites, 840 tests; every scenario, with SL6.3's Alpine initramfs
Linux, KVM, at aaa542897cb, this commit before its fix on 385192f3708: cargo test --all-features --no-fail-fast lib 2297 passed and 28 failed, every one a guest:: test that fails alike at the base 385192f3708 (2296 passed and the same 28 failed, compared by name); cli 120 and every other target passed
Linux, KVM, at aaa542897cb, also: nvx.py test-microvm --backend kvm with sandbox-lifecycle, sandbox-ram-upper, sandbox-gpt-layer, sandbox-proxy-only, sandbox-blocks, managed-exec-config, snapshot-core and filesystem-shares Passed: nvx-rs built at that commit; OpenVMM, the kernel, the Alpine initramfs, and the Ubuntu distro layer and scratch template of CI run 43168368915 at 385192f3708, checked by SHA-256

A code review found that a -- after à, which only Linux splits off, would have passed the first version of the check; the hiding refusal now catches it, with a test. Copilot's review found that the next version, which appended x and required Linux to read it last, took a user's x before a -- for it, as in x\v--; the check now appends nvx_appended, which the user's text cannot hold, and the test adds x -- and x\v--, which the previous version accepted.

The rebases onto c642f09e5ed, which deletes the old clone path, and onto f03c21c3f0c, with SL6.1 (#1520), SL6.12 (#1525, #1527) and the slices before them, touch none of these files and changed nothing of this commit (same patch-id). The fix of Copilot's thread came after them. The rebase onto ff15032cdc5, with SL3.7c (#1502), SL6.11a (#1506), SL6.11c (#1504) and SL3.11a (#1512), changed nothing of this commit either (git range-diff); on it, Clippy --all-targets --all-features with -D warnings for Windows and x86_64-unknown-linux-gnu, the lib's host::cli tests (229) and cli (111), check_test_layout.py and public_api.py passed on Windows.

CI runs the remaining gates, MSHV and WHP among them.

Tests modified

check_test_changes.py --base origin/feature/nvx-refactor lists 2 changes to existing tests and 1 new test:

  • New: starts_check_the_kernel_command_line_that_they_launch (sandbox/lifecycle/start/tests.rs) fills the 1024-byte budget to the byte with and without shares, read-only or read-write, over scratch and in RAM, and refuses one byte more with the old message. It pins the tokens of a read-only export, and the refusals of a NUL, of a virtio-mmio device at the scratch disk's base beside a RAM upper, of a -- after a space, after à and after a vertical tab, whatever parameter precedes it, and of an unclosed quote.
  • cold_share_command_lines_fit_the_kernel_budget_to_the_byte (run/tests.rs) takes export_tokens and COMMAND_LINE_BUDGET in place of the old plan's export_command_line_fragment and SANDBOX_COMMAND_LINE_MAX_SIZE, and pins the tokens of a read-write export, so that nvx-rs run's budget is checked to the same byte.
  • starts_refuse_the_kernel_tokens_of_the_sandbox_profile_and_of_its_mounts (tests/cli/lifecycle.rs) adds quiet -- debug, which a start refuses with the hiding message before it launches anything, without changing the state directory, as it refuses the reserved tokens.

@ppenna Pedro Henrique Penna (ppenna) added tests-modified Changes existing tests, test data, or test wiring feat-nvx-refactor labels Nov 12, 2026
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

This pull request changes existing tests

It modifies or removes 2 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests.

Test code

Change What Before After
modified test fn cold_share_command_lines_fit_the_kernel_budget_to_the_byte in host::cli::run::tests nvx-rs/src/host/cli/run/tests.rs:54 nvx-rs/src/host/cli/run/tests.rs:54
modified test fn starts_refuse_the_kernel_tokens_of_the_sandbox_profile_and_of_its_mounts in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:1391 nvx-rs/tests/cli/lifecycle.rs:1392

Relocated unchanged: 0; added: 1. Compared the merge base ff15032cdc59 with b06d52fa0851.

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

The command-line validation can mistake a user-supplied x for its sentinel, allowing -- to hide required device/bootstrap tokens from Linux and prevent guest boot.

1 open issue
What changed in this PR

Moves sandbox command-line validation to the line rendered by VmConfig, preparing for removal of the legacy launch code in SL6.3.

Changes:

  • Checks the rendered sandbox line’s byte budget, reserved tokens, root compatibility, and visibility of appended tokens.
  • Shares export-token accounting between sandbox launches and nvx-rs run.
  • Adds boundary and refusal tests and updates command-line documentation.
File Description
nvx-rs/​tests/​cli/​lifecycle.rs Adds a start refusal case for --.
nvx-rs/​src/​host/​cli/​sandbox/​state.rs Removes the unused hostname accessor.
nvx-rs/​src/​host/​cli/​sandbox/​mod.rs Adds shared export-bootstrap token rendering.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start/​tests.rs Tests exact byte limits and invalid command lines.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start.rs Validates the rendered VM command line instead of the legacy plan.
nvx-rs/​src/​host/​cli/​run/​tests.rs Updates budget tests and pins writable export tokens.
nvx-rs/​src/​host/​cli/​run.rs Uses the shared export renderer and retained budget constant.
doc/​usage.md Clarifies --cmdline restrictions.
doc/​run.md Documents validation order and quote restrictions.

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.

🟢 Approval recommended

No active review issues were identified for the sandbox command-line validation changes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

`sandbox start` and `sandbox run` refused a kernel command line through
the old launch profile of `host::sandbox`: `check_command_line` built a
`SandboxLaunch` of the configuration, with a `SandboxUpper` and
`SandboxSettings`, bound its plan to the distro binding, and rendered
the plan's kernel command line, which put the user's `--cmdline` first
and left out the managed lifecycle's own tokens. `nvx-rs run` checked
its shares against the kernel's budget with `host::sandbox`'s
`SANDBOX_COMMAND_LINE_MAX_SIZE` and `export_command_line_fragment`.
These were the CLI's last uses of the old launch.

Both now check the command line that they launch, the one that the VM's
`VmConfig` renders, with the CLI's own pieces:

- `check_command_line` takes the sandbox's configuration and its
  `VmConfig`. It refuses the user's part as before: a NUL, and a token
  that the sandbox profile or `--mount` owns, `nvx_*` or `virtfs_*`, as
  Linux or the guest's init reads it, with the same messages. It then
  checks the launched line, with the bootstrap tokens of the shares and
  a NUL, against `host::config::COMMAND_LINE_BUDGET`, the same 1024
  bytes as `SANDBOX_COMMAND_LINE_MAX_SIZE`, and reads the root's boot
  tokens from it with `RootLayout::parse`, with the old messages.
- `export_tokens` renders the bootstrap tokens that OpenVMM appends for
  an export at `/run/nvx/hostfs/root`, as `export_command_line_fragment`
  rendered them, for both checks.

The user's part now follows the root's tokens, which it can no longer
hide, but it precedes the tokens that OpenVMM appends, the virtio-mmio
devices of the root's disks among them. A `--` or an unclosed quote, as
Linux reads them, would hide those, so the check refuses the line that
Linux does not read to its end, where the old check refused one that
hid the root's tokens, and its message names OpenVMM's tokens. That
includes a `--` after a byte that only Linux takes for whitespace, such
as the 0xa0 that ends `à`, which the control console's check of
`VmConfig` does not see, since it splits at ASCII whitespace. The check
appends a parameter that the user's part cannot name, since it holds no
`nvx_*` token, and requires Linux to read it last. Closed double quotes
stay refused as before: `sandbox run` refuses them with
`VmConfig::check_command_line`, which it called already, and a start
when it creates its VM. The user's part is checked as given, not
trimmed, since the launched line holds it as given. `Config::hostname`
loses its last caller and goes.

Docs: `doc/usage.md` and `doc/run.md` say how `--cmdline` is checked.
They still said that quoted values keep their whitespace and that a
`--` or a quote hides the root's tokens.

Tests: `starts_check_the_kernel_command_line_that_they_launch` fills the
budget to the byte with and without shares, over scratch and in RAM,
pins the tokens of a read-only export, and pins the refusals of a NUL,
of a virtio-mmio device at the scratch disk's base beside a RAM upper,
of a `--` after a space, after `à` and after a vertical tab, whatever
parameter precedes it, and of an unclosed quote. `cold_share_command_lines_fit_the_kernel_budget_to_the_byte`
takes the CLI's tokens and budget, and pins the tokens of a read-write
export. `starts_refuse_the_kernel_tokens_of_the_sandbox_profile_and_of_its_mounts`
adds a `--`, which a start refuses before it launches anything, as it
refuses the reserved tokens.

Part of #1120 (SL6.0f).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/slim-sl6-0f-cli-command-line branch from 93469a4 to b06d52f Compare November 12, 2026 10:15
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as ready for review November 12, 2026 10:15
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as draft November 12, 2026 10:17
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as ready for review November 12, 2026 10:18
@ppenna
Pedro Henrique Penna (ppenna) merged commit 230a1b0 into feature/nvx-refactor Nov 12, 2026
106 of 126 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/slim-sl6-0f-cli-command-line branch November 12, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat-nvx-refactor tests-modified Changes existing tests, test data, or test wiring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants