{"record":{"id":"1a395df59ed76e3d","repo":"firecracker-microvm/firecracker","slug":"virtio-ring-access-outside-the-range-validated-at-activation","errorCode":null,"errorMessage":"virtio ring access outside the range validated at activation","messagePattern":"virtio ring access outside the range validated at activation","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/vmm/src/devices/virtio/queue.rs","lineNumber":126,"sourceCode":"    pub next: u16,\n}\n\nimpl DescriptorChain {\n    /// Creates a new `DescriptorChain` from the given descriptor table.\n    ///\n    /// Note that the desc_table and queue_size are assumed to be validated by the caller.\n    fn checked_new(desc_table: GuestMemorySlice, queue_size: u16, index: u16) -> Option<Self> {\n        if queue_size <= index {\n            return None;\n        }\n\n        // SAFETY: initialization aligns the table, and descriptor-sized offsets preserve\n        // alignment. The caller keeps the mapping alive.\n        let desc = unsafe {\n            desc_table\n                .read_obj::<Descriptor>(usize::from(index) * std::mem::size_of::<Descriptor>())\n        }\n        .expect(\"virtio ring access outside the range validated at activation\");\n        let chain = DescriptorChain {\n            desc_table,\n            queue_size,\n            ttl: queue_size,\n            index,\n            addr: GuestAddress(desc.addr),\n            len: desc.len,\n            flags: desc.flags,\n            next: desc.next,\n        };\n\n        if chain.is_valid() { Some(chain) } else { None }\n    }\n\n    fn is_valid(&self) -> bool {\n        !self.has_next() || self.next < self.queue_size\n    }\n","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/firecracker-microvm/firecracker/blob/23b09b943fa3cd4a04004fe55724f0b5c2410f45/src/vmm/src/devices/virtio/queue.rs#L108-L144","documentation":"DescriptorChain::checked_new reads a Descriptor from the guest descriptor table via an unsafe GuestMemorySlice::read_obj whose Result is unwrapped with expect(). The slice's valid range was validated once at device activation, so this expect only fires if the code accesses outside that pre-validated range - an internal invariant violation, not a recoverable guest error. Because the panic aborts the VMM process, it signals a bug in index/offset bookkeeping rather than something callers should catch.","triggerScenarios":"Calling DescriptorChain::checked_new with an index >= queue_size would return None before the read, so the expect fires only if the desc_table slice is smaller than queue_size * size_of::<Descriptor>() - e.g. the slice retained from activation no longer matches the queue size used at pop time, or the table base/length validation at activation was bypassed or mismatched.","commonSituations":"Refactors that change queue size or rebuild GuestMemorySlice ranges without re-validating; device activation code that computes the desc-table length incorrectly; tests/property checkers feeding hand-crafted slices shorter than the declared queue_size.","solutions":["Verify the desc_table GuestMemorySlice passed at activation covers exactly queue_size * size_of::<Descriptor>() bytes from the guest-configured address.","Re-run the activation-time range/alignment validation (length >= RING_HEADER_SIZE + queue_size*stride) after any change to queue size or guest memory layout.","Ensure the same queue_size is used for slice construction and DescriptorChain popping; never let pop paths use a stale or independently computed size.","If you need recoverable behavior, replace the expect with error propagation (e.g. QueueError::DescIndexOutOfBounds) instead of relying on the invariant."],"exampleFix":"// before\nlet desc = unsafe { desc_table.read_obj::<Descriptor>(idx * SIZE) }\n    .expect(\"virtio ring access outside the range validated at activation\");\n// after\nlet desc = unsafe { desc_table.read_obj::<Descriptor>(idx * SIZE) }\n    .map_err(|_| QueueError::DescIndexOutOfBounds(index))?;","handlingStrategy":"validation","validationCode":"fn desc_table_len_ok(len: usize, queue_size: u16) -> bool {\n    len >= queue_size as usize * std::mem::size_of::<Descriptor>()\n}","typeGuard":"fn chain_index_valid(index: u16, queue_size: u16, table_len: usize) -> bool {\n    (index as usize) < queue_size as usize\n        && table_len >= queue_size as usize * std::mem::size_of::<Descriptor>()\n}","tryCatchPattern":"// expect() panics; guard before calling or propagate instead:\nlet chain = DescriptorChain::try_new(desc_table, queue_size, index)\n    .ok_or_else(|| DeviceError::InvalidDescriptor(index))?;","preventionTips":["Validate desc_table length >= queue_size * size_of::<Descriptor>() at activation and fail activation with a typed error.","Share one queue_size constant between ring construction and pop paths.","Re-run validation whenever guest memory or queue configuration changes.","Convert hot-path expects into typed errors so bugs surface as controlled failures."],"tags":["rust","virtio","panic","guest-memory"],"backgroundTag":"internal-invariant-violation","analyzedSha":"23b09b943fa3cd4a04004fe55724f0b5c2410f45","analyzedAt":"2026-09-19T00:01:22.326Z","contentChangedAt":"2026-09-19T00:01:22.326Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}