Skip to content

fix(vhost-user-block): honour a readonly backend - #6083

Merged
ShadowCurse merged 2 commits into
firecracker-microvm:mainfrom
triggerdotdev:fix/vhost-user-blk-read-only
Sep 14, 2026
Merged

ShadowCurse merged 2 commits into
firecracker-microvm:mainfrom
triggerdotdev:fix/vhost-user-blk-read-only

Conversation

@1stvamp

@1stvamp 1stvamp commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

A vhost-user-block device backed by a readonly backend isn't treated as readonly. read_only is always false.

In VhostUserBlockImpl::new:

let avail_features = acked_features;
let acked_features = acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let read_only = acked_features & (1 << VIRTIO_BLK_F_RO) != 0;

The VIRTIO_BLK_F_RO check reads acked_features after it has been reassigned to acked_features & PROTOCOL_FEATURES, so it tests bit 5 against 0x4000_0000 and never matches.

That goes against what the device sets out to do. AVAILABLE_FEATURES includes VIRTIO_BLK_F_RO with the comment "We always try to negotiate readonly with the backend. If the backend is configured as readonly, we will accept it.", and the docs say the same: "Whenever the backend advertises the VIRTIO_BLK_F_RO feature, Firecracker will accept it, and the device will act as readonly." So the documented behaviour was never implemented.

read_only feeds Block::read_only(), which attach_block_devices passes to append_root_device_cmdline, so a readonly vhost-user root device is given rw. To be clear about the blast radius: the guest still sees VIRTIO_BLK_F_RO (that bit reaches it via avail_features) so it retries the root mount readonly and does boot, which is why test_vhost_user_block passes today. What you're left with is a /proc/cmdline that disagrees with reality and a later remount,rw that fails. There's no working around it from config either, since VhostUserBlockConfig::try_from rejects is_read_only for vhost-user drives (it must be None), which leaves the backend as the only intended source of the flag.

The fix computes read_only before acked_features is narrowed, rather than reading avail_features, so the shadowing can't swallow the bit again.

test_new_all_features already advertises VIRTIO_BLK_F_RO from its mock backend and asserts avail_features contains it, so it was asserting the broken result. It now asserts read_only is set. test_new_no_features uses a backend with no features and keeps asserting it is not. test_vhost_user_block already boots a readonly-backend root device via the uvm_vhost_user_booted_ro fixture, so it now also asserts ro reaches the guest cmdline, which is the part no existing test covered.

I found this while working on the generic vhost-user device (#6072), which shares this negotiation code.

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache 2.0 license. For more information on following Developer
Certificate of Origin and signing off your commits, please check
CONTRIBUTING.md.

PR Checklist

  • I have read and understand CONTRIBUTING.md.
  • I have run tools/devtool checkbuild --all to verify that the PR passes
    build checks on all supported architectures.
  • I have run tools/devtool checkstyle to verify that the PR passes the
    automated style checks.
  • I have described what is done in these changes, why they are needed, and
    how they are solving the problem in a clear and encompassing way.
  • I have updated any relevant documentation (both in code and in the docs)
    in the PR.
  • I have mentioned all user-facing changes in CHANGELOG.md.
  • If a specific issue led to this PR, this PR closes the issue.
  • When making API changes, I have followed the
    Runbook for Firecracker API changes.
  • I have tested all new and changed functionalities in unit tests and/or
    integration tests.
  • I have linked an issue to every new TODO.

  • This functionality cannot be added in rust-vmm.

This is part 1 of 4 in a stack made with GitButler:

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.04%. Comparing base (16f9023) to head (bd24d93).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6083   +/-   ##
=======================================
  Coverage   83.04%   83.04%           
=======================================
  Files         277      277           
  Lines       31438    31439    +1     
=======================================
+ Hits        26107    26108    +1     
  Misses       5331     5331           
Flag Coverage Δ
5.10-m5n.metal 83.27% <100.00%> (+<0.01%) ⬆️
5.10-m6a.metal 82.65% <100.00%> (+<0.01%) ⬆️
5.10-m6g.metal 80.11% <100.00%> (-0.01%) ⬇️
5.10-m6i.metal 83.27% <100.00%> (-0.01%) ⬇️
5.10-m7a.metal-48xl 82.64% <100.00%> (-0.01%) ⬇️
5.10-m7g.metal 80.12% <100.00%> (+<0.01%) ⬆️
5.10-m7i.metal-24xl 83.24% <100.00%> (-0.01%) ⬇️
5.10-m7i.metal-48xl 83.24% <100.00%> (-0.01%) ⬇️
5.10-m8g.metal-24xl 80.12% <100.00%> (+<0.01%) ⬆️
5.10-m8g.metal-48xl 80.12% <100.00%> (+<0.01%) ⬆️
5.10-m8i.metal-48xl 83.24% <100.00%> (-0.01%) ⬇️
5.10-m8i.metal-96xl 83.24% <100.00%> (+<0.01%) ⬆️
5.10-m9g.metal-48xl 80.12% <100.00%> (+<0.01%) ⬆️
6.1-m5n.metal 83.29% <100.00%> (+<0.01%) ⬆️
6.1-m6a.metal 82.67% <100.00%> (-0.01%) ⬇️
6.1-m6g.metal 80.11% <100.00%> (-0.01%) ⬇️
6.1-m6i.metal 83.29% <100.00%> (-0.01%) ⬇️
6.1-m7a.metal-48xl 82.66% <100.00%> (-0.01%) ⬇️
6.1-m7g.metal 80.11% <100.00%> (+<0.01%) ⬆️
6.1-m7i.metal-24xl 83.31% <100.00%> (-0.01%) ⬇️
6.1-m7i.metal-48xl 83.31% <100.00%> (+<0.01%) ⬆️
6.1-m8g.metal-24xl 80.11% <100.00%> (-0.01%) ⬇️
6.1-m8g.metal-48xl 80.12% <100.00%> (+<0.01%) ⬆️
6.1-m8i.metal-48xl 83.31% <100.00%> (+<0.01%) ⬆️
6.1-m8i.metal-96xl 83.31% <100.00%> (+<0.01%) ⬆️
6.1-m9g.metal-48xl 80.12% <100.00%> (+<0.01%) ⬆️
6.18-m5n.metal 83.29% <100.00%> (+<0.01%) ⬆️
6.18-m6a.metal 82.67% <100.00%> (-0.01%) ⬇️
6.18-m6g.metal 80.22% <100.00%> (-0.01%) ⬇️
6.18-m6i.metal 83.29% <100.00%> (+<0.01%) ⬆️
6.18-m7a.metal-48xl 82.66% <100.00%> (+<0.01%) ⬆️
6.18-m7g.metal 80.22% <100.00%> (+<0.01%) ⬆️
6.18-m7i.metal-24xl 83.31% <100.00%> (+<0.01%) ⬆️
6.18-m7i.metal-48xl 83.30% <100.00%> (-0.01%) ⬇️
6.18-m8g.metal-24xl 80.22% <100.00%> (+<0.01%) ⬆️
6.18-m8g.metal-48xl 80.22% <100.00%> (+<0.01%) ⬆️
6.18-m8i.metal-48xl 83.31% <100.00%> (-0.01%) ⬇️
6.18-m8i.metal-96xl 83.31% <100.00%> (+<0.01%) ⬆️
6.18-m9g.metal-48xl 80.22% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from 74072c9 to 4a7441f Compare August 17, 2026 12:00
@1stvamp

1stvamp commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

The earlier CI failure here was nothing to do with the change. Build #18251 failed on test_balloon.py::test_stats (free_memory off by 0.2%) on x86_64 and on test_shut_down.py::test_reboot (a 10s process-exit timeout) on aarch64, and neither test touches vhost-user. The optional pipeline passed.

I've rebased onto current main since then, so there's a fresh build queued. Whenever someone has a moment to unblock it, it'd be good to get a clean run on the record.

@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from 4a7441f to 71d04f6 Compare August 20, 2026 20:41
@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from 71d04f6 to cbaeea1 Compare August 25, 2026 01:17
@zulinx86 zulinx86 added the Status: Awaiting assignee Indicates that an issue or pull request is awaiting action from its assignee. label Aug 26, 2026
@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from cbaeea1 to 70e9c01 Compare September 1, 2026 11:16
@Manciukic Manciukic assigned JamesC1305 and unassigned ShadowCurse Sep 2, 2026
@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from 70e9c01 to 3af1a30 Compare September 8, 2026 11:05

@JamesC1305 JamesC1305 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, the change LGTM. Just one suggestion which could maybe improve readability.

Comment thread src/vmm/src/devices/virtio/block/vhost_user/device.rs Outdated
@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from 3af1a30 to fe7fda1 Compare September 8, 2026 12:11
Comment thread tests/integration_tests/functional/test_drive_vhost_user.py Outdated
The VIRTIO_BLK_F_RO check read acked_features after it had been
reassigned to acked_features & PROTOCOL_FEATURES, so it tested bit 5
against 0x4000_0000 and never matched. read_only was therefore always
false, even though the device does ask the backend for VIRTIO_BLK_F_RO,
and docs/api_requests/block-vhost-user.md documents the backend as the
place a readonly vhost-user drive is configured.

read_only feeds Block::read_only(), which decides whether a root device
gets ro or rw on the guest kernel cmdline, so a readonly vhost-user root
device was given rw. The guest still sees VIRTIO_BLK_F_RO and retries
the root mount readonly, so it boots, but /proc/cmdline is wrong and a
later remount,rw fails. There is no way to correct this from config:
VhostUserBlockConfig::try_from rejects is_read_only, which leaves the
backend as the only source of the flag.

Two different values sharing one name is the actual fault, so they are
named apart. negotiate_features now returns backend_acked_features, and
acked_features keeps its meaning of what the guest driver has acked, so
read_only reads the backend set and no ordering constraint is needed.
test_new_all_features already advertises VIRTIO_BLK_F_RO from its mock
backend and asserted the broken result, so it now asserts read_only is
set, and test_vhost_user_block asserts ro reaches the guest cmdline.

Signed-off-by: Wes Mason <wes@1stvamp.org>
@1stvamp
1stvamp force-pushed the fix/vhost-user-blk-read-only branch from fe7fda1 to 003c1b5 Compare September 8, 2026 16:47
@ShadowCurse
ShadowCurse added this pull request to the merge queue Sep 14, 2026
Merged via the queue into firecracker-microvm:main with commit eaba0ed Sep 14, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Awaiting assignee Indicates that an issue or pull request is awaiting action from its assignee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants