Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,20 +523,40 @@ needs one, and a VM that a snapshot restores takes none.

`VmConfig::kernel_command_line` checks the configuration and requires `Boot`,
then renders the root's tokens and settings, the view's tokens, and `nvx_maps`
when an export exists, followed by `Boot.command_line` unchanged. It neither
reads the host nor checks or rewrites caller text. A restore has no kernel
command line from this method and fails with kind `Unsupported`.
when an export exists, followed by accepted `Boot.command_line` text unchanged.
It reads nothing on the host. A restore has no kernel command line from this
method and fails with kind `Unsupported`.

`VmConfig::check` checks the whole value without reading the host: it opens no
path and checks no support of the host. It fails with an `nvx::Error` of kind
`Unsupported` that names the first refusal: no processor or no memory; a layered
root without an image or a scratch path, with a bound of an EROFS image or of 0,
or with a RAM upper's cap outside 1 to 90 percent; a hostname that an activation
record refuses; or an export that `ExportPlan::check` refuses for the guest
targets that the root reserves. Last, it checks a boot's capture with
`CaptureConfig::check`, below, and fails with its error. The network's policy is
checked where it is lowered, with the guest's address. This check does not
inspect the boot's command-line text.
record refuses; an export that `ExportPlan::check` refuses for the guest targets
that the root reserves; a boot's capture that `CaptureConfig::check` refuses;
or caller boot tokens that use a reserved name. The network's policy is checked
where it is lowered, with the guest's address.

The reserved boot names are the root's `nvx_layer`, `nvx_scratch`,
`nvx_overlay_upper`, `nvx_overlay_mode`, `nvx_scratch_journal`,
`nvx_first_partition_bound` and `nvx_ram_upper_percent`; the view's
`nvx_hostname` and `nvx_machine_id`; `nvx_maps`; and every name beginning
`virtfs_`. The check reads tokens as both Linux and the guest's init read them,
with dashes and underscores matching as Linux matches parameter names.
Reserved names stay reserved after `--` and when init's splitting exposes them
inside quoted text. Every other token stays caller-owned, including
`nvx_entrypoint`, `nvx_arg` and `nvx_share`.

`VmConfig::mapping_table` returns exactly the configured
`ExportPlan::guest_table`, in its order, without reading host paths or
revalidating the plan. Without an export it returns an empty table and the
kernel command line omits `nvx_maps`; with an export the token counts those
entries. The table's `HostMap` and its wire codecs are accessible through
`nvx::protocol::maps`, and remain re-exported from `nvx::protocol`.

`COMMAND_LINE_BUDGET`, in `nvx::host::config`, is the 1,024-byte x86 kernel
command-line budget, including the terminating NUL. The legacy launch checks
continue to use it.

A `Boot` may also hold a `CaptureConfig`, what a capture of the VM adds to the
VM's configuration, which keeps the rest, such as the NIC, the console, and the
Expand Down
25 changes: 25 additions & 0 deletions nvx-rs/public-api/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,7 @@ field nvx::host::config::BlockGeometry::physical_block_size
struct nvx::host::config::Boot
field nvx::host::config::Boot::capture
field nvx::host::config::Boot::command_line
const nvx::host::config::COMMAND_LINE_BUDGET
struct nvx::host::config::Capacity
field nvx::host::config::Capacity::boot
field nvx::host::config::Capacity::capacity
Expand Down Expand Up @@ -1804,6 +1805,7 @@ method nvx::host::config::VmConfig::check
field nvx::host::config::VmConfig::console
field nvx::host::config::VmConfig::filesystem
method nvx::host::config::VmConfig::kernel_command_line
method nvx::host::config::VmConfig::mapping_table
field nvx::host::config::VmConfig::memory_mib
field nvx::host::config::VmConfig::network
field nvx::host::config::VmConfig::processors
Expand Down Expand Up @@ -3565,6 +3567,29 @@ struct nvx::protocol::export::HostExport
field nvx::protocol::export::HostExport::mode
method nvx::protocol::export::HostExport::parse
field nvx::protocol::export::HostExport::tag
mod nvx::protocol::maps
struct nvx::protocol::maps::GuestHostMap
field nvx::protocol::maps::GuestHostMap::read_only
field nvx::protocol::maps::GuestHostMap::source
field nvx::protocol::maps::GuestHostMap::target
struct nvx::protocol::maps::GuestMapsPayload
field nvx::protocol::maps::GuestMapsPayload::first
field nvx::protocol::maps::GuestMapsPayload::maps
struct nvx::protocol::maps::HostMap
field nvx::protocol::maps::HostMap::read_only
field nvx::protocol::maps::HostMap::source
field nvx::protocol::maps::HostMap::target
const nvx::protocol::maps::MAPS_HEADER_LEN
const nvx::protocol::maps::MAP_ENTRY_HEADER_LEN
const nvx::protocol::maps::MAP_READ_ONLY
const nvx::protocol::maps::MAX_HOST_MAPPINGS
const nvx::protocol::maps::MAX_MAP_PATH_BYTES
struct nvx::protocol::maps::MapsPayload
field nvx::protocol::maps::MapsPayload::first
field nvx::protocol::maps::MapsPayload::maps
fn nvx::protocol::maps::decode_guest_maps_payload
fn nvx::protocol::maps::decode_maps_payload
fn nvx::protocol::maps::encode_maps_payloads
mod nvx::protocol::nvxs
enum nvx::protocol::nvxs::ErrorCode
variant nvx::protocol::nvxs::ErrorCode::Authentication
Expand Down
25 changes: 25 additions & 0 deletions nvx-rs/public-api/windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ field nvx::host::config::BlockGeometry::physical_block_size
struct nvx::host::config::Boot
field nvx::host::config::Boot::capture
field nvx::host::config::Boot::command_line
const nvx::host::config::COMMAND_LINE_BUDGET
struct nvx::host::config::Capacity
field nvx::host::config::Capacity::boot
field nvx::host::config::Capacity::capacity
Expand Down Expand Up @@ -727,6 +728,7 @@ method nvx::host::config::VmConfig::check
field nvx::host::config::VmConfig::console
field nvx::host::config::VmConfig::filesystem
method nvx::host::config::VmConfig::kernel_command_line
method nvx::host::config::VmConfig::mapping_table
field nvx::host::config::VmConfig::memory_mib
field nvx::host::config::VmConfig::network
field nvx::host::config::VmConfig::processors
Expand Down Expand Up @@ -2487,6 +2489,29 @@ struct nvx::protocol::export::HostExport
field nvx::protocol::export::HostExport::mode
method nvx::protocol::export::HostExport::parse
field nvx::protocol::export::HostExport::tag
mod nvx::protocol::maps
struct nvx::protocol::maps::GuestHostMap
field nvx::protocol::maps::GuestHostMap::read_only
field nvx::protocol::maps::GuestHostMap::source
field nvx::protocol::maps::GuestHostMap::target
struct nvx::protocol::maps::GuestMapsPayload
field nvx::protocol::maps::GuestMapsPayload::first
field nvx::protocol::maps::GuestMapsPayload::maps
struct nvx::protocol::maps::HostMap
field nvx::protocol::maps::HostMap::read_only
field nvx::protocol::maps::HostMap::source
field nvx::protocol::maps::HostMap::target
const nvx::protocol::maps::MAPS_HEADER_LEN
const nvx::protocol::maps::MAP_ENTRY_HEADER_LEN
const nvx::protocol::maps::MAP_READ_ONLY
const nvx::protocol::maps::MAX_HOST_MAPPINGS
const nvx::protocol::maps::MAX_MAP_PATH_BYTES
struct nvx::protocol::maps::MapsPayload
field nvx::protocol::maps::MapsPayload::first
field nvx::protocol::maps::MapsPayload::maps
fn nvx::protocol::maps::decode_guest_maps_payload
fn nvx::protocol::maps::decode_maps_payload
fn nvx::protocol::maps::encode_maps_payloads
mod nvx::protocol::nvxs
enum nvx::protocol::nvxs::ErrorCode
variant nvx::protocol::nvxs::ErrorCode::Authentication
Expand Down
60 changes: 55 additions & 5 deletions nvx-rs/src/host/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ use crate::activation::{self, Journal, MAX_HOSTNAME_BYTES, OverlayMode, View};
use crate::contract;
use crate::error::{Error, Result};
use crate::net::Ipv4Cidr;
use crate::protocol::cmdline::{self, Parameter};
use crate::protocol::maps::HostMap;
use crate::protocol::root::{ImageFormat, RootImage, RootLayout, RootUpper};
use crate::protocol::view;

/// Bytes of the x86 kernel command line available to caller tokens, including the NUL.
pub const COMMAND_LINE_BUDGET: usize = 1024;

/// The guest address of a VM's NIC at a cold boot whose configuration names none, in the form
/// that [`Ipv4Cidr::parse`] reads.
pub const DEFAULT_GUEST_NETWORK: &str = "10.0.0.2/24";
Expand Down Expand Up @@ -139,6 +144,8 @@ pub struct NicConfig {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Boot {
/// The caller's own kernel command-line tokens, to which NVX adds those of what it lowers.
/// The root's tokens, `nvx_hostname`, `nvx_machine_id`, `nvx_maps` and `virtfs_*` are reserved;
/// every other token, including `nvx_entrypoint`, `nvx_arg` and `nvx_share`, is caller-owned.
pub command_line: String,
/// What a capture of the VM adds, if the VM may be captured.
pub capture: Option<CaptureConfig>,
Expand Down Expand Up @@ -358,6 +365,17 @@ impl<'de> serde::Deserialize<'de> for CaptureIdentity {
}

impl VmConfig {
/// Returns the configured export's mapping table, in exactly the order and form of
/// [`ExportPlan::guest_table`], or an empty table without an export.
///
/// This is a pure projection: it reads no host path and does not recheck the plan. A cold
/// boot's rendered `nvx_maps` token counts these entries.
pub fn mapping_table(&self) -> Vec<HostMap> {
self.filesystem
.as_ref()
.map_or_else(Vec::new, ExportPlan::guest_table)
}

/// Renders a cold boot's kernel command line without reading the host: the root's tokens,
/// the view's tokens, the export's mapping count when present, then the caller's
/// [`Boot::command_line`] without trimming or rewriting it.
Expand Down Expand Up @@ -411,13 +429,15 @@ impl VmConfig {
/// refusal in the order of the fields: no processor; no memory; a layered root without an image
/// path, with a bound of an EROFS image's first partition or a bound of 0, with a scratch upper
/// without a path, or with a RAM upper whose cap is not 1 to 90 percent; a hostname that is
/// empty, longer than [`MAX_HOSTNAME_BYTES`], or holds a NUL; and an export that
/// [`ExportPlan::check`] refuses for the root, whose error is the source. Last, it checks a
/// boot's capture with [`CaptureConfig::check`], and fails with its error.
/// empty, longer than [`MAX_HOSTNAME_BYTES`], or holds a NUL; an export that
/// [`ExportPlan::check`] refuses for the root, whose error is the source; a boot's capture
/// that [`CaptureConfig::check`] refuses; and caller boot tokens that collide with the root's,
/// the view's, `nvx_maps`, or any `virtfs_*` name.
///
/// The NIC's address is checked when it is built, and the network's policy where it is
/// lowered, with the guest's address. This check does not inspect [`Boot::command_line`],
/// which [`kernel_command_line`](Self::kernel_command_line) appends unchanged.
/// lowered, with the guest's address. Boot tokens are read as both Linux and the guest's init
/// read them, with dashes and underscores matching as Linux matches parameter names.
/// [`kernel_command_line`](Self::kernel_command_line) appends accepted caller text unchanged.
pub fn check(&self) -> Result<()> {
if self.processors == 0 {
return Err(Error::unsupported("a VM needs at least one processor"));
Expand Down Expand Up @@ -445,6 +465,36 @@ impl VmConfig {
if let Some(capture) = self.boot.as_ref().and_then(|boot| boot.capture.as_ref()) {
capture.check()?;
}
if let Some(boot) = &self.boot {
let guest = cmdline::init_tokens(boot.command_line.as_bytes()).map(|token| Parameter {
name: token.name,
value: token.value,
});
for parameter in cmdline::parameters(boot.command_line.as_bytes()).chain(guest) {
if [
"nvx_layer",
"nvx_scratch",
"nvx_overlay_upper",
"nvx_overlay_mode",
"nvx_scratch_journal",
"nvx_first_partition_bound",
"nvx_ram_upper_percent",
"nvx_hostname",
"nvx_machine_id",
"nvx_maps",
]
.iter()
.any(|name| parameter.is_named(name))
|| parameter.name.starts_with(b"virtfs_")
|| parameter.name.starts_with(b"virtfs-")
{
return Err(Error::unsupported(format!(
"Boot.command_line uses the reserved token {:?}",
String::from_utf8_lossy(parameter.name)
)));
}
}
}
Ok(())
}
}
Expand Down
Loading
Loading