Skip to content

Hypercall to let the VTL0 or VTL1 to retrieve its VMPCK so that it can communicate with the PSP#3408

Open
sunilmut wants to merge 3 commits intomicrosoft:mainfrom
sunilmut:sunilmut/lower_vtl_vmpck
Open

Hypercall to let the VTL0 or VTL1 to retrieve its VMPCK so that it can communicate with the PSP#3408
sunilmut wants to merge 3 commits intomicrosoft:mainfrom
sunilmut:sunilmut/lower_vtl_vmpck

Conversation

@sunilmut
Copy link
Copy Markdown
Member

@sunilmut sunilmut commented Apr 30, 2026

Implement the hypercall that allows the VTL0 or VTL1 retrieve the VM Platform Communication Key (VMPCK) for its own VTL, so that it can directly communicate with the PSP. The VMPCK is used to encrypt/decrypt the communication with the PSP.

Copilot AI review requested due to automatic review settings April 30, 2026 01:26
@sunilmut sunilmut requested a review from a team as a code owner April 30, 2026 01:26
@github-actions
Copy link
Copy Markdown

⚠️ Unsafe Code Detected

This PR modifies files containing unsafe Rust code. Extra scrutiny is required during review.

For more on why we check whole files, instead of just diffs, check out the Rustonomicon

@github-actions github-actions Bot added the unsafe Related to unsafe code label Apr 30, 2026
// just require Underhill setting the apicbase register on the VPs
// before start.
//
// TODO: centralize cpuid querying logic.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This PR took the liberty to centralize the cpuid querying logic (as the PR also needed to make a cpuid call). If the general direction is right, I can try to converge other HCL cpuid calls into the cpuid crate in this PR or a subsequent PR.

sunilmut

This comment was marked as resolved.

@sunilmut sunilmut requested a review from jstarks April 30, 2026 01:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a new Hyper-V hypercall for lower VTL guests (VTL0/VTL1) to retrieve their SEV-SNP VMPCK so they can communicate directly with the PSP, and centralizes some host CPUID feature detection for OpenHCL.

Changes:

  • Added HvCallGetSnpVmpck hypercall definition + output struct in hvdef and dispatch plumbing in hv1_hypercall.
  • Extracted/stored VMPCKs from the SNP secrets page at partition init and exposed them via a new Underhill hypercall handler.
  • Introduced openhcl_cpuid_features crate and plumbed cached CPUID features from worker startup into Hcl.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
vm/x86/x86defs/src/snp.rs Adds SNP secrets-page VMPCK offsets/size constants.
vm/hv1/hvdef/src/lib.rs Extends enlightenment bitfield flags; adds hypercall code + VMPCK output type/size constant.
vm/hv1/hv1_hypercall/src/imp.rs Adds hypercall type alias + handler trait for HvGetSnpVmpck.
openhcl/virt_mshv_vtl/src/processor/snp/mod.rs Caches VMPCK keys from secrets page and implements the new hypercall handler.
openhcl/virt_mshv_vtl/src/lib.rs Plumbs cached CPUID features into Hcl::new on x86_64 guest builds.
openhcl/virt_mshv_vtl/Cargo.toml Adds dependency on openhcl_cpuid_features.
openhcl/underhill_core/src/worker.rs Initializes CPUID feature snapshot once and reuses it for MMIO-hypercall + x2APIC decisions.
openhcl/underhill_core/Cargo.toml Swaps direct CPUID deps for openhcl_cpuid_features.
openhcl/openhcl_cpuid_features/src/lib.rs New crate centralizing a small set of host CPUID feature queries.
openhcl/openhcl_cpuid_features/Cargo.toml New crate manifest with x86_64-only deps.
openhcl/hcl/src/ioctl.rs Adds CPUID feature caching to Hcl and changes Hcl::new signature on x86_64 guest builds.
openhcl/hcl/Cargo.toml Adds openhcl_cpuid_features dependency.
Cargo.toml Registers openhcl_cpuid_features as a workspace member dependency.
Cargo.lock Adds lock entries for the new crate and updated dependency graph.

Comment on lines +471 to +480
let vmpck_keys = if let Some(secrets) = partition_params.snp_secrets {
let mut keys = [[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS];
for (i, key) in keys.iter_mut().enumerate() {
let offset = SNP_SECRETS_VMPCK0_OFFSET + i * SNP_VMPCK_KEY_SIZE;
key.copy_from_slice(&secrets[offset..offset + SNP_VMPCK_KEY_SIZE]);
}
keys
} else {
[[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS]
};
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

partition_params.snp_secrets is optional, but this code silently substitutes all-zero VMPCK keys when it’s missing and also slices secrets[...] without validating the slice length. For SNP partitions, it’s safer to treat a missing/short secrets page as an error (to avoid panics and avoid returning an invalid key later) and validate that the secrets page is large enough for all requested offsets before copying.

Suggested change
let vmpck_keys = if let Some(secrets) = partition_params.snp_secrets {
let mut keys = [[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS];
for (i, key) in keys.iter_mut().enumerate() {
let offset = SNP_SECRETS_VMPCK0_OFFSET + i * SNP_VMPCK_KEY_SIZE;
key.copy_from_slice(&secrets[offset..offset + SNP_VMPCK_KEY_SIZE]);
}
keys
} else {
[[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS]
};
let secrets = partition_params.snp_secrets.ok_or_else(|| {
anyhow::anyhow!("missing SNP secrets page for SNP partition")
})?;
let required_len = SNP_SECRETS_VMPCK0_OFFSET + SNP_NUM_VMPCKS * SNP_VMPCK_KEY_SIZE;
if secrets.len() < required_len {
return Err(anyhow::anyhow!(
"SNP secrets page too short for VMPCK keys: got {} bytes, need at least {}",
secrets.len(),
required_len
));
}
let mut vmpck_keys = [[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS];
for (i, key) in vmpck_keys.iter_mut().enumerate() {
let offset = SNP_SECRETS_VMPCK0_OFFSET + i * SNP_VMPCK_KEY_SIZE;
key.copy_from_slice(&secrets[offset..offset + SNP_VMPCK_KEY_SIZE]);
}

Copilot uses AI. Check for mistakes.
Comment thread vm/hv1/hvdef/src/lib.rs Outdated
Comment thread openhcl/hcl/Cargo.toml
Comment on lines 13 to 16
pal.workspace = true
memory_range.workspace = true
openhcl_cpuid_features.workspace = true
sidecar_client.workspace = true
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

openhcl_cpuid_features is an x86_64-only crate (cfg(target_arch = "x86_64")). Adding it as an unconditional Linux dependency for hcl can cause non-x86_64 Linux builds to fail unless the code is fully cfg-gated or the crate provides non-x86 stubs. Consider moving this dependency under a cfg(target_arch = "x86_64") target section (or otherwise ensuring non-x86 builds don’t pull in/use this crate).

Copilot uses AI. Check for mistakes.
Comment thread openhcl/hcl/src/ioctl.rs
@@ -1346,6 +1346,7 @@ pub struct Hcl {
isolation: IsolationType,
snp_register_bitmap: [u8; 64],
sidecar: Option<SidecarClient>,
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Hcl now stores cpuid_features: openhcl_cpuid_features::CpuidFeatures, but openhcl_cpuid_features is cfg(target_arch = "x86_64") only. As written, this will fail to compile hcl on non-x86_64 targets (or any build where that type isn’t available). Gate this field + accessor behind an appropriate cfg, or provide a non-x86_64 stub type/API in openhcl_cpuid_features so Hcl remains portable.

Suggested change
sidecar: Option<SidecarClient>,
sidecar: Option<SidecarClient>,
#[cfg(target_arch = "x86_64")]

Copilot uses AI. Check for mistakes.
Comment thread openhcl/hcl/src/ioctl.rs
Comment on lines +1736 to +1739
#[cfg(guest_arch = "x86_64")] // xtask-fmt allow-target-arch cpu-intrinsic
pub fn new(
isolation: IsolationType,
sidecar: Option<SidecarClient>,
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Hcl::new is now only defined under #[cfg(guest_arch = "x86_64")], but there are call sites (e.g. the #[cfg(not(guest_arch = "x86_64"))] path in virt_mshv_vtl) that still expect an Hcl::new(isolation, sidecar) constructor. Add a complementary #[cfg(not(guest_arch = "x86_64"))] constructor (or refactor to a single signature) so non-x86_64 guest builds continue to compile.

Suggested change
#[cfg(guest_arch = "x86_64")] // xtask-fmt allow-target-arch cpu-intrinsic
pub fn new(
isolation: IsolationType,
sidecar: Option<SidecarClient>,
pub fn new(
isolation: IsolationType,
sidecar: Option<SidecarClient>,
#[cfg(guest_arch = "x86_64")] // xtask-fmt allow-target-arch cpu-intrinsic

Copilot uses AI. Check for mistakes.
#[cfg(guest_arch = "x86_64")] // xtask-fmt allow-target-arch cpu-intrinsic
let hcl = Hcl::new(hcl_isolation, sidecar, params.cpuid_features).map_err(Error::Hcl)?;
#[cfg(not(guest_arch = "x86_64"))] // xtask-fmt allow-target-arch cpu-intrinsic
let hcl = Hcl::new(hcl_isolation, sidecar).map_err(Error::Hcl)?;
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The #[cfg(not(guest_arch = "x86_64"))] branch still calls Hcl::new(hcl_isolation, sidecar), but Hcl::new appears to be gated to guest_arch = "x86_64" only now. This will break non-x86_64 guest builds unless a non-x86_64 constructor is provided (or this call is updated to match the available API).

Suggested change
let hcl = Hcl::new(hcl_isolation, sidecar).map_err(Error::Hcl)?;
let hcl = Hcl::new(hcl_isolation, sidecar, params.cpuid_features).map_err(Error::Hcl)?;

Copilot uses AI. Check for mistakes.
@sunilmut sunilmut changed the title Hypercall to let the VTL0 or VTL1 to retrieve its VMPCK so that it can communicate with the SNP Hypercall to let the VTL0 or VTL1 to retrieve its VMPCK so that it can communicate with the PSP Apr 30, 2026
{
return Err(HvError::AccessDenied);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

When the secrets page is not set, I will update this hypercall to return a error status.

}
keys
} else {
[[0u8; SNP_VMPCK_KEY_SIZE]; SNP_NUM_VMPCKS]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will change this to store None for secrets so that the hypercall can check and if it is None, return proper error for the hypercall

Comment thread openhcl/hcl/src/ioctl.rs
isolation: IsolationType,
snp_register_bitmap: [u8; 64],
sidecar: Option<SidecarClient>,
cpuid_features: openhcl_cpuid_features::CpuidFeatures,
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.

Do we need to store this? It looks like we're only checking it once in the constructor?

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

Labels

unsafe Related to unsafe code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants