microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
103c0519b1d721f9b666c6e28a329b0fabff3e37

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Guide/src/dev_guide/tests/fuzzing/writing.md

183lines · modecode

1# Writing Fuzzers
2
3## Writing a new fuzzer in OpenVMM
4
5The easiest way to get up and running is to look at the existing in-tree fuzzers
6(which you can list using `cargo xtask fuzz list`), along with reading through
7the [cargo-fuzz book](https://rust-fuzz.github.io/book/cargo-fuzz.html) (the
8book is fairly brief and shouldn't take more than 20 minutes to read through)
9
10Some examples of in-tree fuzzers:
11
12* Simple device example: [chipset/fuzz/battery.rs][fuzz_battery_url]
13* More complex device example: [ide/fuzz/fuzz_ide.rs][fuzz_ide_url]
14* Abstraction over unsafe example: [ide/fuzz/fuzz_scsi_buffer.rs][fuzz_scsi_buffer_url]
15
16[fuzz_battery_url]: https://github.com/microsoft/openvmm/blob/main/vm/devices/chipset/fuzz/fuzz_battery.rs
17[fuzz_ide_url]: https://github.com/microsoft/openvmm/blob/main/vm/devices/storage/ide/fuzz/fuzz_ide.rs
18[fuzz_scsi_buffer_url]: https://github.com/microsoft/openvmm/blob/main/vm/devices/storage/scsi_buffers/fuzz/fuzz_scsi_buffers.rs
19
20Once you're ready to take a stab at writing your own fuzzer, spinning up a new
21fuzzer is as easy as running:
22
23```bash
24cargo xtask fuzz init openvmm_crate_to_fuzz TEMPLATE
25```
26
27Use `--help` for more details on the available TEMPLATE types.
28
29```admonish caution
30We don't suggest using `cargo fuzz init` (i.e: without `xtask`), as it
31emits a template that isn't compatible with the OpenVMM repo style, and also
32doesn't properly update the root Cargo.toml's `workspace.members` array.
33```
34
35## Fuzzing an abstraction over unsafe code
36
37Unsafe code is a prioritized target for fuzzing given its self-evident risks.
38
39However, it can be hard to reason about how best to exercise unsafe code in OpenVMM
40via fuzzing as often (and correctly) the unsafe code is not directly interacting
41with guest-controlled data.
42
43The approach taken with OpenVMM then is to target abstractions over unsafe code.
44Such as interfaces and data structures like `BounceBuffers`, `guest_memory`, or
45`ucs2`. A fuzzer for one of these will attempt to be a regular consumer of the
46abstraction, calling those APIs declared safe and using any data structure in a
47rust-safe way. This attempts to check that the safety guarantees the abstraction
48is making are being upheld via the API.
49
50For example, let's say we want to fuzz `BounceBuffers`, here's what we may
51want to fuzz:
52
53![BounceBuffers Example](./_images//fuzz_abstraction_example.png "Overview of the safe API exposed by BounceBuffers")
54
55Fuzz logic might then allocate a `BounceBuffer` using `new` and call methods on
56it such as `as_mut_bytes` and `io_vecs`. Then it could access the return result
57of both those calls:
58
59```rust
60use scsi_buffers::BounceBuffer;
61
62#[derive(Arbitrary)]
63enum BouneBufferAccess {
64 AsMutBytes,
65 IoVecs
66}
67
68#[derive(Arbitrary)]
69struct FuzzCase {
70 #[arbitrary(with = |u: &mut Unstructured| u.int_in_range(0..=0x40000))]
71 size: usize,
72 accesses: Vec<BounceBufferAccess>
73}
74
75fn access_mut_bytes(buf: &mut [u8]) {
76 buf.fill(b'A');
77 // access buf in other ways to test validity of underlying memory and slice
78}
79
80fn access_io_vecs(io_vecs: &[IoBuffer]) {
81 // access each IoBuffer to test validity of ptr and len
82}
83
84fuzz_target!(|fuzz_case: FuzzCase| { do_fuzz(fuzz_case) });
85
86fn do_fuzz(fuzz_case: FuzzCase) {
87 let mut bb = BounceBuffer::new(fuzz_case.size);
88
89 for access in fuzz_case.accesses {
90 match access {
91 AsMutBytes => {
92 let buf = bb.as_mut_bytes();
93 access_mut_bytes(buf);
94 },
95 IoVecs => {
96 let io_vecs = bb.io_vecs();
97 access_io_vecs(io_vecs)
98 }
99 }
100 }
101})
102```
103
104The fuzzer should work to ensure the safe members of the API cannot be misused
105in any way that may result in memory corruption or unsoundness.
106
107## Fuzzing a chipset device
108
109Writing a fuzzer for a chipset device (e.g: battery, ide, serial, pic,
110etc...) involves targeting the API that is roughly exposed to guests: the
111device's port IO, PCI config, and MMIO interfaces.
112
113While it's entirely possible to hand-roll a fuzzer that is tailored to the
114specific register configuration of a particular device, the in-repo
115`chipset_device_fuzz` crate exports a `FuzzChipset` type that offers a
116"plug-and-play" way to hook a chipset device up to a fuzzer:
117
118```rust
119#[derive(Arbitrary)]
120struct StaticDeviceConfig {
121 #[arbitrary(with = |u: &mut Unstructured| u.int_in_range(0..=16))]
122 num_queues: usize,
123}
124
125fn do_fuzz(u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
126 // Step 1: generate a device's fixed-at-construction-time configuration
127 let static_device_config: StaticDeviceConfig = u.arbitrary()?;
128
129 // Step 2: init the device, and wire-it-up to the fuzz chipset
130 let mut chipset = chipset_device_fuzz::FuzzChipset::default();
131 let my_device = chipset.device_builder("my_dev").add(|services| {
132 my_dev::MyDevice::new(
133 static_device_config.num_queues,
134 &mut services.register_mmio(), // e.g: pci devices have BARs to remap their MMIO intercepts
135 )
136 }).unwrap();
137
138 // Step 3: use the remaining fuzzing input to slam the device with chipset events
139 while !u.is_empty() {
140 let action = chipset.get_arbitrary_action(u)?;
141 xtask_fuzz::fuzz_eprintln!("{:x?}", action); // only prints when running a repro
142 chipset.exec_action(action).unwrap();
143
144 // Step 3.5: (optionally) intersperse "external stimuli" between chipset actions
145 if u.ratio(1, 10)? {
146 let event: u32 = u.arbitrary()?;
147 my_device.report_external_event(event);
148 }
149 }
150
151 Ok(())
152}
153
154fuzz_target!(|input: &[u8]| -> libfuzzer_sys::Corpus {
155 if do_fuzz(&mut Unstructured::new(input)).is_err() {
156 libfuzzer_sys::Corpus::Reject
157 } else {
158 libfuzzer_sys::Corpus::Keep
159 }
160});
161```
162
163## Fuzzing a vmbus device
164
165TBD (no such fuzzers exist in-tree today)
166
167## Fuzzing `async` code
168
169Depending on the nature of the 'async' code in question, there are two main
170recommended approaches to fuzzing it:
171
1721. **now_or_never**: The recommended approach for individual asynchronous calls
173 is to use the `now_or_never` method from the `futures` crate. This method
174 will poll the future to completion, but will not block if the future is
175 not ready to complete. This allows you to fuzz the future without needing
176 to run it to completion, which can be useful for testing the behavior of
177 the future in various states.
178
1792. **DefaultPool::run_with**: The recommended approach for more intricate
180 asynchronous requirements is to use the `DefaultPool::run_with` method from
181 our `pal_async` crate. This method takes a custom async function and runs it
182 to completion. This allows you to write custom code using regular async/await
183 syntax, combinators, `join`s, `select`s, or whatever you wish.
184