Repository navigation
nvx-rs: kernel command-line checks split parameters differently from Linux #723
Description
Summary
The host's checks for reserved parameters in the kernel command lines that nvx-rs builds, such as nvx_* and tsc=, split the command line at whitespace characters. Linux splits it at bytes, honors double quotes, and skips a parameter's leading quote. Byte 0xA0 occurs inside many UTF-8 characters, such as à (C3 A0), so a value can carry a parameter that the checks never see and that the kernel applies.
The host's caller is trusted (doc/design/1.4-trust-boundaries.md), so this is a validation bug rather than a vulnerability. But command lines cross a validation boundary, and invalid configurations must be refused rather than reach the guest.
How Linux splits the command line
In Linux 6.18, whose 6.18.38 release NVX's guests run:
next_argskips a parameter's leading", toggles quoting at every", and ends the parameter at a byte for whichisspace()is true outside quotes.isspace()is true for bytes 0x09 to 0x0D, 0x20, and 0xA0.parse_argsstops at--, and leaves the rest of the command line to init.
What nvx-rs checks
nvx::host::launch::kernel_parameters_problem, whichLaunchSpec::checkandaci_edge_sandboxesapply to extra kernel parameters, splits them withstr::split_whitespace, and refuses quotes,--, andnvx_*,tsc=, andhostname=tokens.SandboxLaunch::kernel_command_linesplits the--cmdlineofnvx-rs sandboxwith Python'sstr.split(), and refusesnvx_*andvirtfs_*tokens. It accepts quotes, which the kernel honors, as ina="b c".- A sandbox's entrypoint, arguments, and mount targets, which become
nvx_entrypoint=,nvx_arg=, andnvx_share=tokens, may not hold Python's whitespace characters, but may hold", and byte 0xA0 inside a character. - In the guest, the time ABI's check of
/proc/cmdlinesplits it at ASCII whitespace, and the guest init splits it at spaces, tabs, and newlines.
Reproduction
A program that calls the library at 8b585d7, and splits the command lines that it builds as next_arg does:
| Input | Verdict | Parameters that the kernel sees |
|---|---|---|
LaunchSpec parameters quietàtsc=unstable |
accepted | quiet\xC3, tsc, hostname |
LaunchSpec parameters quietà-- |
accepted | quiet\xC3, --, hostname |
LaunchSpec parameters quiet tsc=unstable |
refused | |
sandbox --cmdline quietànvx_entrypoint=/bin/other |
accepted | quiet\xC3, nvx_entrypoint, nvx_sandbox, ..., nvx_entrypoint, nvx_hostname |
sandbox --cmdline "nvx_entrypoint=/bin/other" |
accepted | nvx_entrypoint, nvx_sandbox, ..., nvx_entrypoint, nvx_hostname |
sandbox --cmdline quiet nvx_entrypoint=/bin/other |
refused | |
sandbox argument xàtsc=unstable |
accepted | nvx_sandbox, ..., nvx_arg, tsc |
validate_mount_target also accepts /data/àx and /data/a"b.
As a result:
- The kernel applies parameters that
LaunchSpecreserves, such astsc=unstable, which time ABI v1 forbids, and the guest's check of the time ABI doesn't see them either. - A hidden
--ends the kernel's parameters, so the kernel no longer applies thehostname=parameter that NVX appends after the extra parameters. - The kernel splits a sandbox value that NVX meant as one token, and a
"in one joins the tokens after it into one parameter, so the kernel and the guest see different tokens.
The last two follow from the kernel's source; they weren't observed in a guest.
Expected behavior
This is the behavior that #722 settles on:
- Values that become one kernel token, a sandbox's entrypoint, arguments, and mount targets, refuse every byte that the kernel's
isspace()accepts, and". - Command lines that callers write,
--cmdlineandLaunchSpec::kernel_parameters, are split as the kernel splits them, leading quotes included, before reserved parameters are looked for. - The guest's check of the time ABI splits
/proc/cmdlineas the kernel does. - Tests derived from the kernel's rules: a model of
next_argin the tests, and properties that a value meant as one token is one parameter for the kernel, and that no reserved parameter passes, whatever separates it. - Docs: the option rules in
doc/usage.mdanddoc/run.md, and thenvx::host::launchrow of the README's module table.
Acceptance criteria
- Every input that the reproduction accepts is refused, and so are the two mount targets.
- Quoted values in
--cmdline, such asa="b c" d, whichtests/fixtures/cli.jsonrecords, are still accepted.
Activity