From ce93686ad22d9dde10a1fc6c381059f37a2a4549 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 14 Feb 2026 02:35:01 -0500 Subject: [PATCH] block: do not allow guest to not negotiate VIRTIO_BLK_F_RO Unlike most virtio feature bits, VIRTIO_BLK_F_RO is not optional. It indicates that the host is refusing to permit write operations, and the guest must not be allowed to override it. However, the block device currently does not enforce this. If the guest does not negotiate VIRTIO_BLK_F_RO, the block device will think the device is writable and forward write requests to the backend. This is not a security problem right now because the backing device of a read-only device is always opened read-only. The kernel will thus reject the write operations with EBADF. If support is added for receiving the backing device file descriptor via SCM_RIGHTS (#7704), it will be possible to have a read-only block device backed by a writable file descriptor. This would make the bug a genuine security vulnerability. Fix the bug by explicitly checking if VIRTIO_BLK_F_RO was offered but not negotiated. In this case, log a warning and proceed as if the guest did acknowledge the feature. This always indicates a guest driver bug. Fixes: #7697 Signed-off-by: Demi Marie Obenour --- virtio-devices/src/block.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index 06e07eca4..50d8dac65 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -986,6 +986,13 @@ impl VirtioDevice for Block { interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { + // See if the guest didn't ack the device being read-only. + // If so, warn and pretend it did. + let original_acked_features = self.common.acked_features; + self.common.acked_features |= self.common.avail_features & (1u64 << VIRTIO_BLK_F_RO); + if original_acked_features != self.common.acked_features { + warn!("Guest did not acknowledge that device is read-only, acting as if it did!"); + } self.common.activate(&queues, interrupt_cb.clone())?; self.update_writeback();