Skip to content

loader: add support for measured product policies#3572

Open
mayank-microsoft wants to merge 6 commits into
microsoft:mainfrom
mayank-microsoft:container-policy-dynamic-region
Open

loader: add support for measured product policies#3572
mayank-microsoft wants to merge 6 commits into
microsoft:mainfrom
mayank-microsoft:container-policy-dynamic-region

Conversation

@mayank-microsoft

@mayank-microsoft mayank-microsoft commented May 27, 2026

Copy link
Copy Markdown
Contributor

Adds an optional measured ContainerPolicy payload to the paravisor's VTL2 config region, plus its first product CWCOW (Confidential Windows Container on Windows). The policy ismesh-encoded into the measured config region at IGVM-build time and strongly-typed-decoded at boot — giving CWCOW a tamper-evident, attestable place to carry product invariants(read-only VMGS, secure-boot requirements, custom UEFI JSON, etc.) without inventing a new framing layer.

What changes

  • Wire format (vm/loader/loader_defs): introduces ContainerPolicy (mesh oneof) and the CwcowPolicy body. Tags are part of the measured wire format and must not be reused.
  • Measured config region: ParavisorMeasuredVtl2Config gains a container_policy_size: u32. The encoded policy is appended in-place; size 0 means absent. The region is statically PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES page(s); IGVM build hard-panics if a policy overflows, forcing a deliberate SIZE_PAGES bump (which is itself a measurement change).
  • Manifest support (feature-gated manifest): symmetric serde::Serialize/Deserialize on ContainerPolicy with deny_unknown_fields, plus a base64 adapter for custom_uefi_json. The json_round_trip_is_byte_identical test enforces symmetry.
  • CWCOW invariants: custom_uefi_json is mandatory; IGVM build panics on empty input to prevent attested-but-meaningless images.
  • Runtime decode (openhcl/underhill_core): reads the struct, validates container_policy_size <= CONTAINER_POLICY_MAX_SIZE_BYTES, then mesh-decodes. Malformed bytes are a hardboot error.
  • Recipe + manifests: bundled X64CvmCwcow recipe with openhcl-x64-cvm-cwcow-{dev,release}.json manifests as the end-to-end example.

Lab validation (SNP)

Built x64-cvm-cwcow IGVM and booted it on a real SNP VM. uhdiag-dev inspect /vm/measured_vtl2_info surfaced the full decoded CwcowPolicy (vmgs_read_only: true,custom_uefi_json: 151 B, all require_* flags as configured) — confirming the manifest → mesh → measured-region → runtime-decode round trip works end-to-end. Theinspect-surfacing patch itself was not committed; only IGVM-build and runtime decode are shipped.

Adding a new product

See Guide/src/dev_guide/contrib/container_policy.md — two edits in loader_defs/src/paravisor.rs (define body struct, add #[mesh(N)] enum variant). No new dispatch trait, noproduct_id field; the mesh tag is the product identifier and the compiler enforces the strongly-typed body.

Copilot AI review requested due to automatic review settings May 27, 2026 09:36
@github-actions github-actions Bot added the Guide label May 27, 2026

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.

Pull request overview

This PR adds an optional measured ContainerPolicy payload to the fixed VTL2 measured config region, wires it through IGVM generation, and introduces an end-to-end CWCOW (Confidential Windows Container on Windows) recipe + manifests with accompanying runtime parsing and contributor documentation.

Changes:

  • Extend the measured VTL2 config region (now 2 pages) and append an inline mesh-encoded ContainerPolicy body with explicit size framing.
  • Enable manifest-driven ContainerPolicy configuration in igvmfilegen, and encode it into the measured config region during image build.
  • Add runtime decoding support in Underhill plus a new Flowey recipe and Guide documentation for onboarding new container products.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
vm/loader/src/paravisor.rs Builds a fixed-size measured config region image (struct + optional policy bytes) and passes it to import_pages; adds unit tests for encoding/region layout.
vm/loader/loader_defs/src/paravisor.rs Extends ParavisorMeasuredVtl2Config, defines ContainerPolicy wire types + encode/decode helpers, and exports sizing/offset constants.
vm/loader/loader_defs/Cargo.toml Adds feature-gated serde/base64 support for manifest-shaped policy types; adds serde_json for tests.
vm/loader/igvmfilegen/src/main.rs Threads optional ContainerPolicy from manifest config into the OpenHCL loader calls.
vm/loader/igvmfilegen_config/src/lib.rs Extends manifest schema with image.openhcl.container_policy and adds JSON round-trip tests.
vm/loader/igvmfilegen_config/Cargo.toml Enables loader_defs manifest feature so ContainerPolicy can deserialize from JSON.
vm/loader/manifests/openhcl-x64-cvm-cwcow-release.json New release manifest enabling CWCOW container policy for SNP/TDX guest configs.
vm/loader/manifests/openhcl-x64-cvm-cwcow-dev.json New dev manifest enabling CWCOW container policy (and debug) for SNP/TDX/VBS configs.
openhcl/underhill_core/src/loader/vtl2_config/mod.rs Reads container_policy_size, bounds-checks, reads bytes from the measured region, and decodes into MeasuredVtl2Info.
openhcl/underhill_core/src/loader/vtl2_config/container_policy.rs New runtime helper module to decode the measured policy bytes with tests.
Guide/src/SUMMARY.md Adds the new ContainerPolicy contributor page to the Guide navigation.
Guide/src/dev_guide/contrib/container_policy.md New onboarding/format documentation for adding new container products/policies.
flowey/flowey_lib_hvlite/src/build_openhcl_igvm_from_recipe.rs Adds the X64CvmCwcow IGVM recipe wiring to the new manifests.
flowey/flowey_lib_hvlite/src/artifact_openhcl_igvm_from_recipe.rs Adds filename ↔ recipe mappings for the new CWCOW recipe.
flowey/flowey_lib_hvlite/src/_jobs/local_build_igvm.rs Adds local build output naming for the new recipe.
flowey/flowey_hvlite/src/pipelines/build_igvm.rs Adds CLI exposure/plumbing for X64CvmCwcow.
Cargo.lock Records dependency graph changes (notably base64/serde feature usage via loader_defs).

Comment thread openhcl/underhill_core/src/loader/vtl2_config/mod.rs Outdated
Comment thread vm/loader/loader_defs/src/paravisor.rs Outdated
Comment thread Guide/src/dev_guide/contrib/container_policy.md Outdated
Comment thread Guide/src/dev_guide/contrib/container_policy.md Outdated
Comment thread Guide/src/dev_guide/contrib/container_policy.md Outdated
Copilot AI review requested due to automatic review settings May 27, 2026 10: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.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.

Comment thread openhcl/underhill_core/src/loader/vtl2_config/mod.rs
Comment thread vm/loader/src/paravisor.rs Outdated
Comment on lines +462 to +465
Some(
container_policy::read_container_policy(&buf)
.context("container policy decode failed")?,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These changes will be taken up in follow up PRs.

@mayank-microsoft mayank-microsoft changed the title Container policy dynamic region loader: add a new ContainerPolicy payload to Vtl2Config region May 27, 2026
@mayank-microsoft mayank-microsoft marked this pull request as ready for review May 27, 2026 11:01
@mayank-microsoft mayank-microsoft requested a review from a team as a code owner May 27, 2026 11:01
@mayank-microsoft mayank-microsoft changed the title loader: add a new ContainerPolicy payload to Vtl2Config region loader: add a new ContainerPolicy payload to Vtl2Config region in IGVM May 27, 2026
@mayank-microsoft

Copy link
Copy Markdown
Contributor Author

@chris-oo @sunilmut I have taken an approach to append the new policy after vtl2 config structure ends. Right now, we don't need more than the existing page, tomorrow we can increase the config region pages constant value and the solution should scale. We hard exit if the policy a user is trying to add goes beyond 1 page today.

@github-actions

Copy link
Copy Markdown

X64CvmDevkern,
/// X64 OpenHCL with CVM support and the CWCOW (Confidential Windows
/// Container on Windows) container policy enabled.
X64CvmCwcow,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just call it X64Cwcow ?

Comment thread vm/loader/loader_defs/src/paravisor.rs Outdated
pub padding: [u8; 7],
/// Byte length of the inline [`ContainerPolicy`] body, or `0` if
/// absent.
pub container_policy_size: u32,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My proposal is to keep the variable part generic and not hard code any specific product related stuff here. I have been working with AI to create some implementation for the concepts I had in mind for this. Take a look

6c34d3a

The entry point to look at is the definition of ParavisorMeasuredVtl2Config

We can discuss this together

mayank-microsoft and others added 2 commits June 1, 2026 11:56
Add an optional, measured ProductPolicy payload to the paravisor's
VTL2 measured config region, and a first product variant CWCOW
(Confidential Windows Container on Windows). This gives CWCOW a
tamper-evident, attestable place to carry product invariants
(VMGS read-only, secure-boot requirements, custom UEFI JSON, ...).

Wire format
-----------
* `ParavisorMeasuredVtl2Config` (in loader_defs::paravisor, repr(C))
  gains `product_policy_size: u32`. The encoded policy is appended
  in-place immediately after the struct; size 0 means absent.
* Region is statically `PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES`
  pages (currently 1). IGVM build panics if the encoded policy
  overflows, forcing a deliberate page-count bump (which is itself
  a measurement change).
* `ProductPolicy` is a mesh-protobuf oneof; tags are part of the
  measured wire format and must never be reused.
* CWCOW's `custom_uefi_json` field is `#[mesh(6)]` and mandatory at
  build time: encode panics if empty, to prevent attested-but-
  meaningless images.

Crate structure
---------------
New crate `openhcl/openhcl_product_policy/`:
  * `wire.rs`   - `ProductPolicy` enum, decode error, encode/decode
                  fns, `ProductPolicy::name() -> &'static str`
  * `cwcow.rs`  - `CwcowPolicy` struct, base64 serde adapter,
                  `CwcowPolicyView` (via `product_view!` macro),
                  hand-written `validate_secure_boot_enabled`
  * `lib.rs`    - `product_view!` declarative macro (emits view
                  scaffolding + module-level `pub fn policy()`),
                  `OnceLock<Option<ProductPolicy>>` global, idempotent
                  `init()` / `get()` with CVM_ALLOWED-gated info log
                  of the variant tag (or "none")

Runtime install
---------------
`underhill_core::loader::vtl2_config::read_vtl2_params` decodes the
optional ProductPolicy from the measured page and installs it via
`openhcl_product_policy::init(...)`. The decoded value is intentionally
not stored on `MeasuredVtl2Info` - consumers reach the policy via
the global.

Consumer API (used by follow-up commits as enforcement points land):

    openhcl_product_policy::cwcow::policy()
        .validate_secure_boot_enabled(secure_boot_on)?;

Empty view (no policy installed, or installed policy is for a
different product) makes every `validate_*` a no-op, so consumers
call unconditionally without "is CWCOW active?" branching.

loader_defs shrinks
-------------------
`loader_defs` keeps only the region layout struct
(`ParavisorMeasuredVtl2Config`) and the two layout constants
(`PRODUCT_POLICY_INLINE_OFFSET`, `PRODUCT_POLICY_MAX_SIZE_BYTES`).
The `product_policy/` directory, the five re-exports, and the
`manifest` feature have all moved into `openhcl_product_policy`.

Manifest support
----------------
Manifest JSON deserializes directly into the wire enum via serde
(`rename_all = "snake_case"`, `deny_unknown_fields`). `custom_uefi_json`
is base64-encoded in JSON via a symmetric `serialize`/`deserialize`
adapter; `json_round_trip_is_byte_identical` enforces symmetry.

Bundled `X64CvmCwcow` recipe + `openhcl-x64-cvm-cwcow-{dev,release}.json`
manifests demonstrate the full pipeline.

Documentation
-------------
`Guide/src/dev_guide/contrib/product_policy.md` documents the wire
schema, region layout, runtime decode behaviour, and the "Adding a
new product" walkthrough.

Lab validation (SNP)
--------------------
Built and booted `openhcl-x64-cvm-cwcow.bin` on a real SNP VM. With
a temporary inspect-projection patch applied, `uhdiag-dev inspect`
surfaced all six decoded `CwcowPolicy` fields (including the 151-byte
`custom_uefi_json`), confirming the full manifest -> mesh ->
measured-region -> runtime-decode round trip.

Verified locally:
 * cargo build of loader_defs, openhcl_product_policy (manifest +
   inspect + std), loader, igvmfilegen, igvmfilegen_config,
   underhill_core
 * cargo test on each (18/18 in openhcl_product_policy, 3/3 in
   loader_defs paravisor layout, 14/14 in loader, 6/6 in
   igvmfilegen_config)
 * cargo doc -p openhcl_product_policy --features std,manifest,inspect
 * cargo xflowey build-igvm x64-cvm-cwcow produces a valid IGVM
   end-to-end from the renamed manifest

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mayank-microsoft mayank-microsoft force-pushed the container-policy-dynamic-region branch from c6884f7 to 18e5ef7 Compare June 1, 2026 12:50
The three rust fenced blocks in this chapter are illustrative
fragments (orphan field declarations, abstract `FooPolicy` /
`CwcowPolicy`, references to `mesh_protobuf` / `serde` / `base64`
which rustdoc doesn't link). Mark them as `rust,ignore` so
`mdbook test Guide` highlights but does not compile them.

Verified: `mdbook test Guide` completes without errors.

Signed-off-by: mayank-microsoft <mayank@microsoft.com>
Copilot AI review requested due to automatic review settings June 1, 2026 13:02

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.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 5 comments.

Comment on lines +149 to +160
let policy = policy_bytes.unwrap_or(&[]);
config.product_policy_size = policy.len() as u32;

let buf_bytes = (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize);
let mut buf = vec![0u8; buf_bytes];

let struct_bytes = config.as_bytes();
buf[..struct_bytes.len()].copy_from_slice(struct_bytes);
if !policy.is_empty() {
let off = PRODUCT_POLICY_INLINE_OFFSET;
buf[off..off + policy.len()].copy_from_slice(policy);
}
Comment on lines +476 to +479
if let Err(_) = openhcl_product_policy::init(product_policy) {
anyhow::bail!("conflicting product policy already installed");
}

Comment on lines +9 to +31
[features]
# Pull in serde::Serialize/Deserialize derives on the wire types
# (manifest authoring). Mirrors the same-named feature that previously
# lived on `loader_defs`.
manifest = ["dep:serde", "dep:base64"]
# Derive `inspect::Inspect` on the wire types.
inspect = ["dep:inspect"]
# Enables the OnceLock-backed global (init/get) and the
# anyhow-returning validate_* methods on the product views. Only
# OpenHCL runtime crates enable this.
std = ["dep:anyhow"]

[dependencies]
mesh_protobuf.workspace = true

# CVM_ALLOWED-gated tracing in `init()`.
cvm_tracing.workspace = true
tracing.workspace = true

anyhow = { workspace = true, optional = true }
base64 = { workspace = true, optional = true, features = ["alloc"] }
inspect = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["alloc", "derive"] }
Comment on lines +224 to +228
`encode_product_policy_bytes` will `panic!` at IGVM-build time with
a message that names `PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES`. The
fix is to bump that constant (e.g. from 1 to 2) in
`openhcl/openhcl_product_policy/src/wire.rs`. Bumping it is a measurement
change — every IGVM, with or without a configured policy, will have a
Comment on lines +264 to +265
[`ParavisorMeasuredVtl2Config`]: https://openvmm.dev/rustdoc/openhcl_product_policy/struct.ParavisorMeasuredVtl2Config.html
[`ProductPolicy`]: https://openvmm.dev/rustdoc/openhcl_product_policy/enum.ProductPolicy.html
Copilot AI review requested due to automatic review settings June 1, 2026 13:35

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.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 3 comments.

Comment on lines +75 to +78
pub fn init(policy: Option<ProductPolicy>) -> Result<(), ()> {
match POLICY.get() {
Some(existing) if *existing == policy => Ok(()),
Some(_) => Err(()),
Comment on lines +225 to +228
a message that names `PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES`. The
fix is to bump that constant (e.g. from 1 to 2) in
`openhcl/openhcl_product_policy/src/wire.rs`. Bumping it is a measurement
change — every IGVM, with or without a configured policy, will have a
Comment on lines +264 to +265
[`ParavisorMeasuredVtl2Config`]: https://openvmm.dev/rustdoc/openhcl_product_policy/struct.ParavisorMeasuredVtl2Config.html
[`ProductPolicy`]: https://openvmm.dev/rustdoc/openhcl_product_policy/enum.ProductPolicy.html
@mayank-microsoft mayank-microsoft changed the title loader: add a new ContainerPolicy payload to Vtl2Config region in IGVM loader: add support for measured product policies Jun 1, 2026
use cvm_tracing::CVM_ALLOWED;
use std::sync::OnceLock;

static POLICY: OnceLock<Option<ProductPolicy>> = OnceLock::new();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we not have this as global but instead store it in the vm object that gets created?

Copilot AI review requested due to automatic review settings June 10, 2026 18:00

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.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 8 comments.

Comment on lines 360 to +364
/// Paravisor measured config for vtl2.
///
/// Followed in place by the optional [`ProductPolicy`] body at
/// [`PRODUCT_POLICY_INLINE_OFFSET`]; `product_policy_size == 0` (the
/// pre-feature zero-filled tail) means absent.
Comment on lines +375 to +377
/// Byte length of the inline [`ProductPolicy`] body, or `0` if
/// absent.
pub product_policy_size: u32,
Comment on lines +149 to +161
let policy = policy_bytes.unwrap_or(&[]);
config.product_policy_size = policy.len() as u32;

let buf_bytes = (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize);
let mut buf = vec![0u8; buf_bytes];

let struct_bytes = config.as_bytes();
buf[..struct_bytes.len()].copy_from_slice(struct_bytes);
if !policy.is_empty() {
let off = PRODUCT_POLICY_INLINE_OFFSET;
buf[off..off + policy.len()].copy_from_slice(policy);
}
buf
Comment on lines +90 to +93
## Adding a new product

The default flow is two edits in `openhcl/openhcl_product_policy/src/wire.rs`:

Comment on lines +223 to +227
`encode_product_policy_bytes` will `panic!` at IGVM-build time with
a message that names `PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES`. The
fix is to bump that constant (e.g. from 1 to 2) in
`openhcl/openhcl_product_policy/src/wire.rs`. Bumping it is a measurement
change — every IGVM, with or without a configured policy, will have a
Comment on lines +263 to +264
[`ParavisorMeasuredVtl2Config`]: https://openvmm.dev/rustdoc/openhcl_product_policy/struct.ParavisorMeasuredVtl2Config.html
[`ProductPolicy`]: https://openvmm.dev/rustdoc/openhcl_product_policy/enum.ProductPolicy.html
Comment on lines +115 to +120
define_product_policy! {
package = "openhcl.product_policy";

/// Sivm.
1 => Sivm(sivm::SivmPolicy);
}
Comment on lines +389 to +394
/// Byte offset of the inline [`ProductPolicy`] body within the
/// measured VTL2 config region.
pub const PRODUCT_POLICY_INLINE_OFFSET: usize = size_of::<ParavisorMeasuredVtl2Config>();

/// Maximum byte size of an inline [`ProductPolicy`] body.
pub const PRODUCT_POLICY_MAX_SIZE_BYTES: usize =
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants