Skip to content

openvmm: host CPUID with 0 or over 32768 VPs per socket panics topology #1378

Open

Description

@tangruize

TopologyBuilder::from_cpuid accepts a VPs-per-socket count derived from host CPUID without checking the builder's own range, 1 to 32768, so such a host's VM panics later, in debug and release builds. A count of 0 divides by zero when the topology is built. A count above 32768 panics when it is encoded into an AMD guest's CPUID (an Intel guest gets truncated counts instead). The standard (non-microVM) machine and OpenHCL start from the host's topology (from_host_topology), so only a host, or a hypervisor presenting one, whose CPUID gives such a count reaches this. The microVM starts from TopologyBuilder::new_x86() and is not affected (openvmm/openvmm_core/src/worker/dispatch/microvm.rs).

Reproduction

Expected: from_cpuid refuses both of these hosts. Actual:

  • A host whose leaf 0xB core level has an x2APIC shift of 16 gives 1 << 16 VPs per socket, and from_cpuid and build(1) succeed. With an AMD guest CPUID, topology_cpuid then panics with "value out of bounds [0, 15]", encoding the count into leaf 0x80000008's 4-bit APIC core ID size.
  • An AMD host without leaf 0xB, whose leaf 0x80000008 reports NC=0 and whose leaf 0x8000001E reports two threads per compute unit, gives 1 / 2 = 0, and build(1) then panics with "attempt to divide by zero".

The new test host_topology_refuses_out_of_range_vps_per_socket feeds both layouts to from_cpuid.

Cause

TopologyBuilder::from_cpuid (vm/vmcore/vm_topology/src/processor/x86.rs) checks only threads_per_core before it stores vps_per_socket, so it bypasses the 1..=32768 range that the vps_per_socket() setter clamps to:

// vm/vmcore/vm_topology/src/processor/x86.rs
if threads_per_core > 2 {
    return Err(HostTopologyError::UnsupportedThreadsPerCore(
        threads_per_core,
    ));
}

Ok(Self {
    smt_enabled: threads_per_core > 1 && vps_per_socket > 1,
    vps_per_socket,
    arch: Default::default(),
})

Fix

fix.patch makes from_cpuid return the new HostTopologyError::UnsupportedVpsPerSocket for a count outside 1..=32768, so such a host's VM fails to start with that error instead of panicking. It refuses rather than clamps, so that the guest's topology does not change silently. The bounds 1 and 32768 stay accepted. The test covers both failing layouts and both bounds. The patch adds test_with_tracing as a dev-dependency of vm_topology (with its Cargo.lock line).

Related: #1379.

Activity

  1. tangruize commented on Nov 8, 2026

    @tangruize
    ContributorAuthor

    Proposed patch

    Patch against OpenVMM 3cf58a5234ed824435825c9f87187f5129219724. Save the contents below as fix.patch and run git apply fix.patch.

    fix.patch
    diff --git a/Cargo.lock b/Cargo.lock
    index 962a90d..3adb5a4 100644
    --- a/Cargo.lock
    +++ b/Cargo.lock
    @@ -11057,6 +11057,7 @@ dependencies = [
      "memory_range",
      "mesh_protobuf",
      "safe_intrinsics",
    + "test_with_tracing",
      "thiserror 2.0.16",
      "x86defs",
     ]
    diff --git a/vm/vmcore/vm_topology/Cargo.toml b/vm/vmcore/vm_topology/Cargo.toml
    index b461798..df1e196 100644
    --- a/vm/vmcore/vm_topology/Cargo.toml
    +++ b/vm/vmcore/vm_topology/Cargo.toml
    @@ -27,5 +27,8 @@ safe_intrinsics.workspace = true
     [build-dependencies]
     build_rs_guest_arch.workspace = true
     
    +[dev-dependencies]
    +test_with_tracing.workspace = true
    +
     [lints]
     workspace = true
    diff --git a/vm/vmcore/vm_topology/src/processor.rs b/vm/vmcore/vm_topology/src/processor.rs
    index 6472447..302b82d 100644
    --- a/vm/vmcore/vm_topology/src/processor.rs
    +++ b/vm/vmcore/vm_topology/src/processor.rs
    @@ -71,6 +71,9 @@ pub enum HostTopologyError {
         /// The host topology has more than 2 threads per core.
         #[error("unsupported thread-per-core count {0}")]
         UnsupportedThreadsPerCore(u32),
    +    /// The host topology's VP-per-socket count is outside the builder's range.
    +    #[error("unsupported VP-per-socket count {0}")]
    +    UnsupportedVpsPerSocket(u32),
     }
     
     /// Error when building a [`ProcessorTopology`].
    diff --git a/vm/vmcore/vm_topology/src/processor/x86.rs b/vm/vmcore/vm_topology/src/processor/x86.rs
    index 1b0d59b..658ad6d 100644
    --- a/vm/vmcore/vm_topology/src/processor/x86.rs
    +++ b/vm/vmcore/vm_topology/src/processor/x86.rs
    @@ -178,6 +178,10 @@ pub fn from_cpuid(
                 ));
             }
     
    +        if !(1..=32768).contains(&vps_per_socket) {
    +            return Err(HostTopologyError::UnsupportedVpsPerSocket(vps_per_socket));
    +        }
    +
             Ok(Self {
                 smt_enabled: threads_per_core > 1 && vps_per_socket > 1,
                 vps_per_socket,
    @@ -316,3 +320,55 @@ pub enum ApicMode {
         #[cfg_attr(feature = "inspect", inspect(rename = "x2apic_enabled"))]
         X2ApicEnabled,
     }
    +
    +#[cfg(test)]
    +mod tests {
    +    use super::*;
    +    use test_with_tracing::test;
    +
    +    fn intel_host_cpuid(core_shift: u32) -> impl FnMut(u32, u32) -> [u32; 4] {
    +        move |leaf, subleaf| match (leaf, subleaf) {
    +            (0, _) => [
    +                0xb,
    +                u32::from_le_bytes(*b"Genu"),
    +                u32::from_le_bytes(*b"ntel"),
    +                u32::from_le_bytes(*b"ineI"),
    +            ],
    +            (0xb, 0) => [1, 0, 1 << 8, 0],
    +            (0xb, 1) => [core_shift, 0, 2 << 8, 0],
    +            _ => [0; 4],
    +        }
    +    }
    +
    +    fn amd_host_cpuid(nc: u8) -> impl FnMut(u32, u32) -> [u32; 4] {
    +        move |leaf, _| match leaf {
    +            0 => [
    +                0xa,
    +                u32::from_le_bytes(*b"Auth"),
    +                u32::from_le_bytes(*b"cAMD"),
    +                u32::from_le_bytes(*b"enti"),
    +            ],
    +            0x8000_0000 => [0x8000_001e, 0, 0, 0],
    +            0x8000_0008 => [0, 0, nc.into(), 0],
    +            0x8000_001e => [0, 2 << 8, 0, 0],
    +            _ => [0; 4],
    +        }
    +    }
    +
    +    #[test]
    +    fn host_topology_refuses_out_of_range_vps_per_socket() {
    +        assert!(matches!(
    +            TopologyBuilder::from_cpuid(&mut intel_host_cpuid(16)),
    +            Err(HostTopologyError::UnsupportedVpsPerSocket(65536))
    +        ));
    +        let builder = TopologyBuilder::from_cpuid(&mut intel_host_cpuid(15)).unwrap();
    +        assert_eq!(builder.vps_per_socket, 32768);
    +        assert!(matches!(
    +            TopologyBuilder::from_cpuid(&mut amd_host_cpuid(0)),
    +            Err(HostTopologyError::UnsupportedVpsPerSocket(0))
    +        ));
    +        let builder = TopologyBuilder::from_cpuid(&mut amd_host_cpuid(1)).unwrap();
    +        assert_eq!(builder.vps_per_socket, 1);
    +        builder.build(1).unwrap();
    +    }
    +}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions