Skip to content

Rudimentary NVMe emulation fuzzer - #966

Merged
iximeow merged 4 commits into
masterfrom
ixi/nvme-fuzz-baseline
Sep 30, 2026
Merged

iximeow merged 4 commits into
masterfrom
ixi/nvme-fuzz-baseline

Conversation

@iximeow

@iximeow iximeow commented Oct 28, 2025

Copy link
Copy Markdown
Member

This tries doing a bunch of random operations against an NVMe device and checks the operations against a limited model of what the results of those operations should be.

The initial stab at this is what caught #965, and it caught a bug in an intermediate state of #953 (which other phd tests did notice anyway). This fuzzing would probably be best with actual I/O operations mixed in, and I think that should be relatively straightforward to add from here., but as-is it's useful!

This would probably be best phrased as a cargo-fuzz test to at least get coverage-guided fuzzing. Because of the statefulness of NVMe I think either way we'd want the model of expected device state and a pick-actions-then-run execution to further guide cargo-fuzz into useful parts of the device state.

The initial approach at this allowed for device reset and migration at arbitrary times via a separate thread. When that required synchronizing the model of device state it was effectively interleaved with "guest" operations on the device, and in practice admin commands are serialized by the NvmeCtrl state lock anyway. It may be more interesting to revisit with concurrent I/O operations on submission/completion queues.

This tries doing a bunch of random operations against an NVMe device and
checks the operations against a limited model of what the results of
those operations should be.

The initial stab at this is what caught
#965, and it caught a bug
in an intermediate state of #953 (which other phd tests did notice
anyway). This fuzzing would probably be best with actual I/O operations
mixed in, and I think that *should* be relatively straightforward to add
from here., but as-is it's useful!

This would probably be best phrased as a `cargo-fuzz` test to at least
get coverage-guided fuzzing. Because of the statefulness of NVMe I
think either way we'd want the model of expected device state and a
pick-actions-then-run execution to further guide `cargo-fuzz` into
useful parts of the device state.

The initial approach at this allowed for device reset and migration at
arbitrary times via a separate thread. When that required synchronizing
the model of device state it was effectively interleaved with "guest"
operations on the device, and in practice admin commands are serialized
by the `NvmeCtrl` state lock anyway. It may be more interesting to
revisit with concurrent I/O operations on submission/completion queues.
@iximeow
iximeow requested review from hawkw and pfmooney October 28, 2025 18:20

let mut rng = Pcg64::seed_from_u64(seed);

for _ in 0..1_000 {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cargo test gets through this in about 4 seconds, but cargo test --release is .. almost immediate. I'd been running this at 100k iterations instead, there. Even a thousand seems like an OK place to be for CI?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

mayhaps an env var?

@iximeow
iximeow requested review from jordanhendricks and removed request for pfmooney November 13, 2025 21:38
/// any point as `PciNvme` technically does. (In practice, reset immediately
/// locks the inner `NvmeCtrl` to do the reset, for administrative options it is
/// effectively serialized anyway.)
struct FuzzCtx {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'd talked with Patrick just a bit about the idea of having a more composable fuzzing framework as part of Propolis (the library). the implementation in this file is simultaneously:

  • an NVMe driver, which is driven ~randomly to exercise propolis/src/hw/nvme
  • a model of the NVMe controller state, based on the driver's operations (what queues are created, is the device initialized, etc)
  • a model of the synthesis of NVMe spec and controller model: given the current state, what NVMe operations should have what outcomes, and what state transitions are permissible from the current state?

you could swap out the word "NVMe" above for any other devices (the chipset would be interesting!). a reasonable thing to want would be fuzzing a pair of NVMe devices concurrently on the same PCI bridge. or poking a disk and NIC concurrently, or configuring a pair of NVMe devices to write on each others' queues, or ...

in the limit this seems to me like assembling an increasingly chaotic VM and configuring how chaotic it should be. I don't plan on adjusting this in that direction right now, but it seems like an interesting future direction this could take.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this idea, but I also agree that we should try to get this in before generalizing it.

@jordanhendricks jordanhendricks 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.

I gave this a look and broadly it looks good, but I think I'm lacking some understanding of how we intend to use this. Is the idea that we will run a fuzzer in CI? Or as a standalone tool?

@hawkw hawkw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

neat!

/// any point as `PciNvme` technically does. (In practice, reset immediately
/// locks the inner `NvmeCtrl` to do the reset, for administrative options it is
/// effectively serialized anyway.)
struct FuzzCtx {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this idea, but I also agree that we should try to get this in before generalizing it.

Comment on lines +116 to +117
// 64 MB feels like a reasonable (but very tiny!) size for a test
// disk.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

are there compelling reasons to vary the block size and size of the test disk, in future?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

block size definitely, disk size less probably? since then we've gotten a new parameter on PciNvme for "does the device have a volatile write cache", which won't affect disk semantics and we should be able to change arbitrarily.

but changing these generally I'm not sure how to fit in quite yet.. probably want that kind of fuzzing in an enclosing loop that tries 1k operations or whatever, rather than (for example) Init possibly changing the controller like this. in my mind the current sequence of operations is a stand-in for a single guest operating on a single disk, so changing those parameters are some other variable?

Comment thread lib/propolis/src/hw/nvme/test.rs Outdated
Comment on lines +160 to +163
// I/O submission/completion queues are interleaved (for fun more than
// anything else). With 256kb of memory for queues we can have
// up to 64 I/O queues in the form of 32 submission and completion
// queues.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

turbo nitpick: mayhaps we could have a const IO_QUEUE_REGION_SIZE = 256 and derive the 32 et al from that (and use that 256 in the address calculation above)? that way there's one constant to mess with and everything else just works.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

since the size of this region is a function of the queue size and queue count I turned this around a bit, there's now an IO_QUEUEPAIR_MAX that with the queue size const drives IO_QUEUES_SIZE and places IO_QUEUES_BASE. since I was messing with the const there's now const asserts that the region size and placement is some constants, so that the hex values you might see in debugging are written out somewhere in the source..

@iximeow
iximeow force-pushed the ixi/nvme-fuzz-baseline branch from dafaa36 to cb63659 Compare September 30, 2026 00:41
@iximeow
iximeow force-pushed the ixi/nvme-fuzz-baseline branch from d5c6b3e to 8fc9550 Compare September 30, 2026 01:03
@iximeow

iximeow commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

how we intend to use this. Is the idea that we will run a fuzzer in CI? Or as a standalone tool?

so! I'm planning on merging this imminently as I've since expanded on this a bit. to your question from ... last year ..., my hope is that we should always be able to do 1k Thingies to a disk in CI, and if not that's a remarkable problem. but whatever we're doing in CI should be pretty short. we might want to concurrently cargo test --release with more operations, maybe? none of the device emulation testing (at least for nvme) relies on illumos so maybe we do that as another job to be on Linux and run wherever, rather than holding an illumos CI target for longer.

I also hope we can use this to build a standalone tool that runs longer (or more exotic) configurations. but the part in CI here is intended to be pretty short and really a smoke-test that things basically work.

@pfmooney

Copy link
Copy Markdown
Contributor

I also hope we can use this to build a standalone tool that runs longer (or more exotic) configurations. but the part in CI here is intended to be pretty short and really a smoke-test that things basically work.

This would be the sort of thing I'd be thinking about following up with (eventually).

strong endorse

@iximeow
iximeow merged commit 12ff010 into master Sep 30, 2026
14 checks passed
@iximeow
iximeow deleted the ixi/nvme-fuzz-baseline branch September 30, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants