Skip to content

openvmm: Intel CPUID generation panics above 4096 reserved VPs per socket #1379

Open

Description

@tangruize

Generating a partition's topology CPUID from an Intel host's leaf 04h panics when the topology reserves more than 4096 VPs per socket, even if the VM has only one actual VP. The panic is value out of bounds [0, 4095] from a bitfield setter, in debug and release builds. It aborts VM creation on an Intel host with KVM (without a time ABI) or WHP, which build the topology CPUID from the host's. The topology builder accepts up to 32768 VPs per socket (--vps-per-socket), so this is the operator's configuration, before the guest runs, not anything a guest can reach. The microVM, with at most 8 VPs in one socket, is not affected.

Reproduction

Build a topology with TopologyBuilder::new_x86().vps_per_socket(8192).x2apic(X2ApicState::Supported).build(1), and call topology_cpuid with an Intel CPUID that has an L3 descriptor in leaf 04h. Expected: a saturated sharing count. Actual: the panic value out of bounds [0, 4095]. The new test intel_l3_sharing_saturates_above_4096_vps_per_socket covers 4096, 8192 and 32768.

Cause

cache_parameters_cpuid (vmm_core/virt/src/x86/topology.rs) saturates the core-count field, and AMD's leaf 8000001Dh saturates its sharing count, but the L3 sharing count here goes to the 12-bit set_threads_sharing_cache_minus_one as it is:

// vmm_core/virt/src/x86/topology.rs
} else {
    eax.set_cores_per_socket_minus_one(min(
        MAX_CORES_PER_SOCKET_MINUS_ONE,
        topology.reserved_vps_per_socket() - 1,
    ));
// ...
if CacheParametersEax::from(result[0]).cache_level() == 3 {
    eax.set_threads_sharing_cache_minus_one(topology.reserved_vps_per_socket() - 1);
}

Fix

fix.patch encodes min(0xfff, reserved_vps_per_socket() - 1), the same as AMD's saturation. Above 4096 VPs per socket the L3 cache then reports 4096 sharing logical processors, the field's maximum; counts up to 4096 are unchanged, and the descriptor's other fields, including the native cache level, are kept. The test checks the encoded value for each count.

Related: #1378.

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/vmm_core/virt/src/x86/topology.rs b/vmm_core/virt/src/x86/topology.rs
    index 87686f0..e5594c2 100644
    --- a/vmm_core/virt/src/x86/topology.rs
    +++ b/vmm_core/virt/src/x86/topology.rs
    @@ -204,7 +204,11 @@ fn cache_parameters_cpuid(
             // The level comes from the cache's own descriptor: `eax` carries only
             // the topology fields.
             if CacheParametersEax::from(result[0]).cache_level() == 3 {
    -            eax.set_threads_sharing_cache_minus_one(topology.reserved_vps_per_socket() - 1);
    +            // The sharing count has 12 bits, as on AMD's leaf 8000001Dh.
    +            eax.set_threads_sharing_cache_minus_one(min(
    +                0xfff,
    +                topology.reserved_vps_per_socket() - 1,
    +            ));
             }
     
             let eax_mask = CacheParametersEax::new()
    @@ -367,6 +371,7 @@ fn amd_processor_topology_definition_cpuid(
     mod tests {
         use super::*;
         use crate::CpuidLeafSet;
    +    use test_with_tracing::test;
         use vm_topology::processor::TopologyBuilder;
         use vm_topology::processor::x86::X2ApicState;
     
    @@ -422,6 +427,23 @@ fn cache_results_apply_to_their_own_subleaf() {
             assert_eq!(set.result(4, 4, &[0; 4]), [0; 4]);
         }
     
    +    #[test]
    +    fn intel_l3_sharing_saturates_above_4096_vps_per_socket() {
    +        for vps_per_socket in [4096, 8192, 32768] {
    +            let topology = TopologyBuilder::new_x86()
    +                .vps_per_socket(vps_per_socket)
    +                .x2apic(X2ApicState::Supported)
    +                .build(1)
    +                .unwrap();
    +            let mut leaves = Vec::new();
    +            topology_cpuid(&topology, &intel_cpuid, &mut leaves).unwrap();
    +            let set = CpuidLeafSet::new(leaves);
    +            let eax = CacheParametersEax::from(set.result(4, 3, &intel_cpuid(4, 3))[0]);
    +            assert_eq!(eax.cache_level(), 3);
    +            assert_eq!(eax.threads_sharing_cache_minus_one(), 0xfff);
    +        }
    +    }
    +
         /// The L1 and L2 caches belong to a core, and the L3 cache to the socket.
         /// Leaf 4 counts addressable IDs, so the socket's VP count is rounded up
         /// to a power of two.
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