Repository: firecracker-microvm/firecracker
File: src/vmm/src/devices/virtio/queue.rs (Rust)

Code before:
            .get_slice_ptr(mem, self.avail_ring_address, self.avail_ring_size())?
            .cast();
        self.used_ring_ptr = self
            .get_slice_ptr(mem, self.used_ring_address, self.used_ring_size())?
            .cast();

        // read_volatile() method is called on avail_ring_ptr and used_ring_ptr and it assumes that
        // a pointer of interest is aligned properly; otherwise results in a panic. Such an
        // unalignment is possible when restored from a broken/fuzzed snapshot. Both
        // `avail_ring_ptr` and `used_ring_ptr` must be aligned at least for u16 (2-byte alignment)
        // as documented in `struct Queue`.
        if !self.avail_ring_ptr.is_aligned() {
            return Err(QueueError::PointerNotAligned(
                "Queue::avail_ring_ptr".into(),
            ));
        }
        if !self.used_ring_ptr.cast::<u16>().is_aligned() {
            return Err(QueueError::PointerNotAligned("Queue::used_ring_ptr".into()));
        }

        // Disable it for kani tests, otherwise it will hit this assertion
        // and fail.
        #[cfg(not(kani))]
        if self.actual_size() < self.len() {
            return Err(QueueError::InvalidQueueSize(self.len(), self.actual_size()));
        }

        Ok(())
    }

    /// Mark memory used for queue objects as dirty.
    pub fn mark_memory_dirty<M: GuestMemory>(&self, mem: &M) -> Result<(), QueueError> {
        _ = self.get_slice_ptr(mem, self.desc_table_address, self.desc_table_size())?;
        _ = self.get_slice_ptr(mem, self.avail_ring_address, self.avail_ring_size())?;
        _ = self.get_slice_ptr(mem, self.used_ring_address, self.used_ring_size())?;
        Ok(())
    }

    /// Get AvailRing.idx
    #[inline(always)]
    pub fn avail_ring_idx_get(&self) -> u16 {
        // SAFETY: `idx` is 1 u16 away from the start
        unsafe { self.avail_ring_ptr.add(1).read_volatile() }
    }

    /// Get element from AvailRing.ring at index
    /// # Safety
    /// The `index` parameter should be in 0..queue_size bounds
    #[inline(always)]
    unsafe fn avail_ring_ring_get(&self, index: usize) -> u16 {
        // SAFETY: `ring` is 2 u16 away from the start

Review comment (suggestion):
Let's add check for `desc_table_ptr` as well. Also these alignment requirements should hold regardless of `read_volatile` usage e.g they always must be aligned, so the comment can be simplified to that requirement.

Code after:
            .get_slice_ptr(mem, self.avail_ring_address, self.avail_ring_size())?
            .cast();
        self.used_ring_ptr = self
            .get_slice_ptr(mem, self.used_ring_address, self.used_ring_size())?
            .cast();

        // All the above pointers are expected to be aligned properly; otherwise some methods (e.g.
        // `read_volatile()`) will panic. Such an unalignment is possible when restored from a
        // broken/fuzzed snapshot.
        //
        // Specification of those pointers' alignments
        // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-350007
        // > ================ ==========
        // > Virtqueue Part    Alignment
        // > ================ ==========
        // > Descriptor Table 16
        // > Available Ring   2
        // > Used Ring        4
        // > ================ ==========
        if !self.desc_table_ptr.cast::<u128>().is_aligned() {
            return Err(QueueError::PointerNotAligned(
                self.desc_table_ptr as usize,
                16,
            ));
        }
        if !self.avail_ring_ptr.is_aligned() {
            return Err(QueueError::PointerNotAligned(
                self.avail_ring_ptr as usize,
                2,
            ));
        }
        if !self.used_ring_ptr.cast::<u32>().is_aligned() {
            return Err(QueueError::PointerNotAligned(
                self.used_ring_ptr as usize,
                4,
            ));
        }

        Ok(())
    }

    /// Mark memory used for queue objects as dirty.
    pub fn mark_memory_dirty<M: GuestMemory>(&self, mem: &M) -> Result<(), QueueError> {
        _ = self.get_slice_ptr(mem, self.desc_table_address, self.desc_table_size())?;
        _ = self.get_slice_ptr(mem, self.avail_ring_address, self.avail_ring_size())?;
        _ = self.get_slice_ptr(mem, self.used_ring_address, self.used_ring_size())?;
        Ok(())
    }

    /// Get AvailRing.idx
    #[inline(always)]