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 <demiobenour@gmail.com>
This commit is contained in:
Demi Marie Obenour 2026-02-14 02:35:01 -05:00 committed by Rob Bradford
parent 80ea1fe62d
commit ce93686ad2

View file

@ -986,6 +986,13 @@ impl VirtioDevice for Block {
interrupt_cb: Arc<dyn VirtioInterrupt>,
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();