Skip to main content

chipset_device_fuzz/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A chipset for fuzz-testing devices.
5
6#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8
9use chipset_arc_mutex_device::device::ArcMutexChipsetDeviceBuilder;
10use chipset_arc_mutex_device::device::ArcMutexChipsetServicesFinalize;
11use chipset_arc_mutex_device::services::ChipsetServices;
12use chipset_arc_mutex_device::services::ChipsetServicesMeta;
13use chipset_arc_mutex_device::services::MmioInterceptServices;
14use chipset_arc_mutex_device::services::PciConfigSpaceServices;
15use chipset_arc_mutex_device::services::PollDeviceServices;
16use chipset_arc_mutex_device::services::PortIoInterceptServices;
17use chipset_device::ChipsetDevice;
18use chipset_device::io::IoError;
19use chipset_device::io::IoResult;
20use chipset_device::io::deferred::DeferredToken;
21use chipset_device::mmio::ControlMmioIntercept;
22use chipset_device::mmio::RegisterMmioIntercept;
23use chipset_device::pci::ByteEnabledDwordRead;
24use chipset_device::pci::ByteEnabledDwordWrite;
25use chipset_device::pio::ControlPortIoIntercept;
26use chipset_device::pio::RegisterPortIoIntercept;
27use closeable_mutex::CloseableMutex;
28use parking_lot::RwLock;
29use range_map_vec::RangeMap;
30use std::cell::Cell;
31use std::collections::BTreeMap;
32use std::sync::Arc;
33use std::sync::Weak;
34use std::task::Context;
35use std::task::Poll;
36use std::task::Waker;
37
38type InterceptRanges<U> =
39    Arc<RwLock<RangeMap<U, (Box<str>, Weak<CloseableMutex<dyn ChipsetDevice>>)>>>;
40
41/// A chipset for fuzz-testing devices.
42///
43/// Intelligently generates MMIO/PIO/PCI accesses based on what interfaces the
44/// device supports, and what intercepts the device has configured.
45///
46/// Resilient against runtime remapping of intercept regions.
47#[derive(Default)]
48pub struct FuzzChipset {
49    devices: Vec<Arc<CloseableMutex<dyn ChipsetDevice>>>,
50    mmio_ranges: InterceptRanges<u64>,
51    pio_ranges: InterceptRanges<u16>,
52    pci_devices: BTreeMap<(u8, u8, u8), Weak<CloseableMutex<dyn ChipsetDevice>>>,
53    poll_devices: Vec<Weak<CloseableMutex<dyn ChipsetDevice>>>,
54    max_defer_poll_count: usize,
55}
56
57impl FuzzChipset {
58    /// Construct a new `FuzzChipset`. Any asynchronous operations will be polled
59    /// at most `max_poll_count` times before panicking.
60    pub fn new(max_poll_count: usize) -> Self {
61        Self {
62            devices: Default::default(),
63            mmio_ranges: Default::default(),
64            pio_ranges: Default::default(),
65            pci_devices: Default::default(),
66            poll_devices: Default::default(),
67            max_defer_poll_count: max_poll_count,
68        }
69    }
70
71    /// Return a device builder associated with the chipset
72    pub fn device_builder<T: ChipsetDevice>(
73        &mut self,
74        name: &'static str,
75    ) -> ArcMutexChipsetDeviceBuilder<FuzzChipsetServicesImpl<'_>, T> {
76        ArcMutexChipsetDeviceBuilder::new(name.into(), |dev, _name| {
77            FuzzChipsetServicesImpl::new(self, dev)
78        })
79    }
80
81    /// Dispatch a MMIO read to the given address.
82    fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Option<()> {
83        // devices might want to map/unmap ranges as part of a MMIO access,
84        // so don't hold the range lock for any longer than we need to
85        let dev = self.mmio_ranges.read().get(&addr)?.1.upgrade().unwrap();
86        let mut locked_dev = dev.lock();
87        let result = locked_dev
88            .supports_mmio()
89            .expect("objects on the mmio bus support mmio")
90            .mmio_read(addr, data);
91        // Convert to a non-deferred result
92        let result = match result {
93            IoResult::Ok => Ok(()),
94            IoResult::Err(e) => Err(e),
95            IoResult::Defer(t) => self.defer_read_now_or_never(&mut *locked_dev, t, data),
96        };
97        match result {
98            Ok(()) => {}
99            Err(_) => {
100                data.fill(!0);
101            }
102        }
103        Some(())
104    }
105
106    /// Dispatch a MMIO write to the given address.
107    fn mmio_write(&self, addr: u64, data: &[u8]) -> Option<()> {
108        // devices might want to map/unmap ranges as part of a MMIO access,
109        // so don't hold the range lock for any longer than we need to
110        let dev = self.mmio_ranges.read().get(&addr)?.1.upgrade().unwrap();
111        let mut locked_dev = dev.lock();
112        let result = locked_dev
113            .supports_mmio()
114            .expect("objects on the mmio bus support mmio")
115            .mmio_write(addr, data);
116        match result {
117            IoResult::Ok => {}
118            IoResult::Err(_) => {}
119            IoResult::Defer(t) => {
120                let _ = self.defer_write_now_or_never(&mut *locked_dev, t);
121            }
122        }
123        Some(())
124    }
125
126    /// Dispatch a port io read to the given address.
127    fn pio_read(&self, addr: u16, data: &mut [u8]) -> Option<()> {
128        // devices might want to map/unmap ranges as part of a pio access,
129        // so don't hold the range lock for any longer than we need to
130        let dev = self.pio_ranges.read().get(&addr)?.1.upgrade().unwrap();
131        let mut locked_dev = dev.lock();
132        let result = locked_dev
133            .supports_pio()
134            .expect("objects on the pio bus support pio")
135            .io_read(addr, data);
136        // Convert to a non-deferred result
137        let result = match result {
138            IoResult::Ok => Ok(()),
139            IoResult::Err(e) => Err(e),
140            IoResult::Defer(t) => self.defer_read_now_or_never(&mut *locked_dev, t, data),
141        };
142        match result {
143            Ok(()) => {}
144            Err(_) => {
145                data.fill(!0);
146            }
147        }
148        Some(())
149    }
150
151    /// Dispatch a port io write to the given address.
152    fn pio_write(&self, addr: u16, data: &[u8]) -> Option<()> {
153        // devices might want to map/unmap ranges as part of a pio access,
154        // so don't hold the range lock for any longer than we need to
155        let dev = self.pio_ranges.read().get(&addr)?.1.upgrade().unwrap();
156        let mut locked_dev = dev.lock();
157        let result = locked_dev
158            .supports_pio()
159            .expect("objects on the pio bus support pio")
160            .io_write(addr, data);
161        match result {
162            IoResult::Ok => {}
163            IoResult::Err(_) => {}
164            IoResult::Defer(t) => {
165                let _ = self.defer_write_now_or_never(&mut *locked_dev, t);
166            }
167        }
168        Some(())
169    }
170
171    /// Dispatch a PCI read to the given device + offset.
172    fn pci_read(&self, bdf: (u8, u8, u8), offset: u16, data: &mut [u8]) -> Option<()> {
173        let dev = self.pci_devices.get(&bdf)?.upgrade().unwrap();
174        let mut locked_dev = dev.lock();
175        let mut value = 0;
176        let result = locked_dev
177            .supports_pci()
178            .expect("objects on the pci bus support pci")
179            .pci_cfg_read(
180                offset,
181                ByteEnabledDwordRead::with_all_bytes_enabled(&mut value),
182            );
183        // Convert to a non-deferred result
184        let result = match result {
185            IoResult::Ok => Ok(()),
186            IoResult::Err(e) => Err(e),
187            IoResult::Defer(t) => self.defer_read_now_or_never(&mut *locked_dev, t, data),
188        };
189        match result {
190            Ok(()) => data.copy_from_slice(&value.to_ne_bytes()[..data.len()]),
191            Err(_) => {
192                data.fill(0);
193            }
194        }
195        Some(())
196    }
197
198    /// Dispatch a PCI write to the given device + offset.
199    fn pci_write(&self, bdf: (u8, u8, u8), offset: u16, value: u32) -> Option<()> {
200        let dev = self.pci_devices.get(&bdf)?.upgrade().unwrap();
201        let mut locked_dev = dev.lock();
202        let result = locked_dev
203            .supports_pci()
204            .expect("objects on the pci bus support pci")
205            .pci_cfg_write(offset, ByteEnabledDwordWrite::with_all_bytes_enabled(value));
206        match result {
207            IoResult::Ok => {}
208            IoResult::Err(_) => {}
209            IoResult::Defer(t) => {
210                let _ = self.defer_write_now_or_never(&mut *locked_dev, t);
211            }
212        }
213        Some(())
214    }
215
216    /// Poll the given device.
217    fn poll_device(&self, index: usize) -> Option<()> {
218        self.poll_devices[index]
219            .upgrade()
220            .unwrap()
221            .lock()
222            .supports_poll_device()
223            .expect("objects supporting polling support polling")
224            .poll_device(&mut Context::from_waker(Waker::noop()));
225        Some(())
226    }
227
228    /// Poll a deferred read once, panic if it isn't complete afterwards.
229    fn defer_read_now_or_never(
230        &self,
231        dev: &mut dyn ChipsetDevice,
232        mut t: DeferredToken,
233        data: &mut [u8],
234    ) -> Result<(), IoError> {
235        let mut cx = Context::from_waker(Waker::noop());
236        let dev = dev
237            .supports_poll_device()
238            .expect("objects returning a DeferredToken support polling");
239        // Some devices (like IDE) will limit the amount of work they perform in a single poll
240        // even though forward progress is still possible. We poll the device multiple times
241        // to let these actions complete. If the action is still pending after all these polls
242        // we know that something is actually wrong.
243        for _ in 0..self.max_defer_poll_count {
244            dev.poll_device(&mut cx);
245            match t.poll_read(&mut cx, data) {
246                Poll::Ready(r) => return r,
247                Poll::Pending => {}
248            }
249        }
250        if self.max_defer_poll_count == 0 {
251            panic!(
252                "Device operation returned a deferred read. Call FuzzChipset::new and set a non-zero max_poll_count to poll async operations."
253            );
254        } else {
255            panic!(
256                "Device operation returned a deferred read that didn't complete after {} polls",
257                self.max_defer_poll_count
258            )
259        }
260    }
261
262    /// Poll a deferred write once, panic if it isn't complete afterwards.
263    fn defer_write_now_or_never(
264        &self,
265        dev: &mut dyn ChipsetDevice,
266        mut t: DeferredToken,
267    ) -> Result<(), IoError> {
268        let mut cx = Context::from_waker(Waker::noop());
269        let dev = dev
270            .supports_poll_device()
271            .expect("objects returning a DeferredToken support polling");
272        // Some devices (like IDE) will limit the amount of work they perform in a single poll
273        // even though forward progress is still possible. We poll the device multiple times
274        // to let these actions complete. If the action is still pending after all these polls
275        // we know that something is actually wrong.
276        for _ in 0..self.max_defer_poll_count {
277            dev.poll_device(&mut cx);
278            match t.poll_write(&mut cx) {
279                Poll::Ready(r) => return r,
280                Poll::Pending => {}
281            }
282        }
283        if self.max_defer_poll_count == 0 {
284            panic!(
285                "Device operation returned a deferred write. Call FuzzChipset::new and set a non-zero max_poll_count to poll async operations."
286            );
287        } else {
288            panic!(
289                "Device operation returned a deferred write that didn't complete after {} polls",
290                self.max_defer_poll_count
291            )
292        }
293    }
294
295    /// Intelligently suggest a random `ChipsetAction`, based on the currently
296    /// registered devices, intercept regions, etc...
297    pub fn get_arbitrary_action(
298        &self,
299        u: &mut arbitrary::Unstructured<'_>,
300    ) -> arbitrary::Result<ChipsetAction> {
301        #[derive(arbitrary::Arbitrary)]
302        enum ChipsetActionKind {
303            MmioRead,
304            MmioWrite,
305            PortIoRead,
306            PortIoWrite,
307            PciRead,
308            PciWrite,
309            Poll,
310        }
311
312        let action_kind: ChipsetActionKind = u.arbitrary()?;
313        let action = match action_kind {
314            ChipsetActionKind::MmioRead | ChipsetActionKind::MmioWrite => {
315                let active_ranges = self
316                    .mmio_ranges
317                    .read()
318                    .iter()
319                    .map(|(r, _)| r)
320                    .collect::<Vec<_>>();
321                let range = u.choose(&active_ranges)?;
322
323                let addr = u.int_in_range(range.clone())?;
324                let len = *u.choose(&[1, 2, 4, 8])?;
325
326                if matches!(action_kind, ChipsetActionKind::MmioRead) {
327                    ChipsetAction::MmioRead { addr, len }
328                } else {
329                    let val = u.bytes(len)?.to_vec();
330                    ChipsetAction::MmioWrite { addr, val }
331                }
332            }
333            ChipsetActionKind::PortIoRead | ChipsetActionKind::PortIoWrite => {
334                let active_ranges = self
335                    .pio_ranges
336                    .read()
337                    .iter()
338                    .map(|(r, _)| r)
339                    .collect::<Vec<_>>();
340                let range = u.choose(&active_ranges)?;
341
342                let addr = u.int_in_range(range.clone())?;
343                let len = *u.choose(&[1, 2, 4])?;
344
345                if matches!(action_kind, ChipsetActionKind::PortIoRead) {
346                    ChipsetAction::PortIoRead { addr, len }
347                } else {
348                    let val = u.bytes(len)?.to_vec();
349                    ChipsetAction::PortIoWrite { addr, val }
350                }
351            }
352            ChipsetActionKind::PciRead | ChipsetActionKind::PciWrite => {
353                let attached_bdfs = self.pci_devices.keys().collect::<Vec<_>>();
354                let bdf = **u.choose(&attached_bdfs)?;
355
356                let offset = u.int_in_range(0..=1023)? * 4; // pci-e max cfg space size
357
358                if matches!(action_kind, ChipsetActionKind::PciRead) {
359                    ChipsetAction::PciRead { bdf, offset }
360                } else {
361                    ChipsetAction::PciWrite {
362                        bdf,
363                        offset,
364                        val: u.arbitrary()?,
365                    }
366                }
367            }
368            ChipsetActionKind::Poll => {
369                let index = u.choose_index(self.poll_devices.len())?;
370                ChipsetAction::Poll { index }
371            }
372        };
373
374        Ok(action)
375    }
376
377    /// Execute the provided `ChipsetAction`
378    pub fn exec_action(&self, action: ChipsetAction) -> Option<()> {
379        let mut buf = [0; 8];
380        match action {
381            ChipsetAction::MmioRead { addr, len } => self.mmio_read(addr, &mut buf[..len]),
382            ChipsetAction::MmioWrite { addr, val } => self.mmio_write(addr, &val),
383            ChipsetAction::PortIoRead { addr, len } => self.pio_read(addr, &mut buf[..len]),
384            ChipsetAction::PortIoWrite { addr, val } => self.pio_write(addr, &val),
385            ChipsetAction::PciRead { bdf, offset } => self.pci_read(bdf, offset, &mut buf[..4]),
386            ChipsetAction::PciWrite { bdf, offset, val } => self.pci_write(bdf, offset, val),
387            ChipsetAction::Poll { index } => self.poll_device(index),
388        }
389    }
390}
391
392#[derive(Debug)]
393pub enum ChipsetAction {
394    MmioRead {
395        addr: u64,
396        len: usize,
397    },
398    MmioWrite {
399        addr: u64,
400        val: Vec<u8>,
401    },
402    PortIoRead {
403        addr: u16,
404        len: usize,
405    },
406    PortIoWrite {
407        addr: u16,
408        val: Vec<u8>,
409    },
410    PciRead {
411        bdf: (u8, u8, u8),
412        offset: u16,
413    },
414    PciWrite {
415        bdf: (u8, u8, u8),
416        offset: u16,
417        val: u32,
418    },
419    Poll {
420        index: usize,
421    },
422}
423
424/// A concrete type which implements [`RegisterMmioIntercept`]
425pub struct FuzzRegisterIntercept<U> {
426    dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
427    map: InterceptRanges<U>,
428}
429
430// Implementation detail - the concrete type returned by TestMmioRangeMapper's
431// `new_io_region` implementation
432struct FuzzControlIntercept<U> {
433    map: InterceptRanges<U>,
434    region_name: Box<str>,
435    len: U,
436    addr: Option<U>,
437    io: Weak<CloseableMutex<dyn ChipsetDevice>>,
438}
439
440macro_rules! impl_intercept {
441    ($register_trait:ident, $control_trait:ident, $register:ident, $control:ident, $usize:ty) => {
442        pub type $register = FuzzRegisterIntercept<$usize>;
443        type $control = FuzzControlIntercept<$usize>;
444
445        impl $register_trait for $register {
446            fn new_io_region(&mut self, region_name: &str, len: $usize) -> Box<dyn $control_trait> {
447                Box::new($control {
448                    map: self.map.clone(),
449                    region_name: region_name.into(),
450                    len,
451                    addr: None,
452                    io: self.dev.clone(),
453                })
454            }
455        }
456
457        impl $control_trait for $control {
458            fn region_name(&self) -> &str {
459                &self.region_name
460            }
461
462            fn map(&mut self, addr: $usize) {
463                self.unmap();
464                if self.map.write().insert(
465                    addr..=addr
466                        .checked_add(self.len - 1)
467                        .expect("overflow during addition, not possible in real hardware"),
468                    (self.region_name.clone(), self.io.clone()),
469                ) {
470                    self.addr = Some(addr);
471                } else {
472                    tracing::trace!("{}::map failed", stringify!($control));
473                }
474            }
475
476            fn unmap(&mut self) {
477                if let Some(addr) = self.addr.take() {
478                    let _entry = self.map.write().remove(&addr).unwrap();
479                }
480            }
481
482            fn addr(&self) -> Option<$usize> {
483                self.addr
484            }
485
486            fn len(&self) -> $usize {
487                self.len
488            }
489
490            fn offset_of(&self, addr: $usize) -> Option<$usize> {
491                let base = self.addr?;
492
493                (base..(base + self.len))
494                    .contains(&addr)
495                    .then(|| addr - base)
496            }
497        }
498    };
499}
500
501impl_intercept!(
502    RegisterMmioIntercept,
503    ControlMmioIntercept,
504    FuzzRegisterMmioIntercept,
505    FuzzControlMmioIntercept,
506    u64
507);
508impl_intercept!(
509    RegisterPortIoIntercept,
510    ControlPortIoIntercept,
511    FuzzRegisterPortIoIntercept,
512    FuzzControlPortIoIntercept,
513    u16
514);
515
516/// Implementation of [`ChipsetServices`] associated with [`FuzzChipset`]
517pub struct FuzzChipsetServicesImpl<'a> {
518    vm_chipset: &'a mut FuzzChipset,
519    dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
520    took_mmio: Cell<bool>,
521    took_pio: Cell<bool>,
522    took_pci: Cell<bool>,
523    took_poll: Cell<bool>,
524}
525
526impl<'a> FuzzChipsetServicesImpl<'a> {
527    pub fn new(
528        vm_chipset: &'a mut FuzzChipset,
529        dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
530    ) -> Self {
531        Self {
532            vm_chipset,
533            dev,
534            took_mmio: false.into(),
535            took_pio: false.into(),
536            took_pci: false.into(),
537            took_poll: false.into(),
538        }
539    }
540}
541
542/// Compile-time type metadata used by [`FuzzChipsetServicesImpl`]'s
543/// [`ChipsetServices`] impl
544pub enum FuzzChipsetServicesMeta {}
545impl ChipsetServicesMeta for FuzzChipsetServicesMeta {
546    type RegisterMmioIntercept = FuzzRegisterMmioIntercept;
547    type RegisterPortIoIntercept = FuzzRegisterPortIoIntercept;
548}
549
550impl ChipsetServices for FuzzChipsetServicesImpl<'_> {
551    type M = FuzzChipsetServicesMeta;
552
553    #[inline(always)]
554    fn supports_mmio(&mut self) -> Option<&mut dyn MmioInterceptServices<M = Self::M>> {
555        Some(self)
556    }
557
558    #[inline(always)]
559    fn supports_pio(&mut self) -> Option<&mut dyn PortIoInterceptServices<M = Self::M>> {
560        Some(self)
561    }
562
563    #[inline(always)]
564    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpaceServices<M = Self::M>> {
565        Some(self)
566    }
567
568    #[inline(always)]
569    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDeviceServices<M = Self::M>> {
570        Some(self)
571    }
572}
573
574impl<T: ChipsetDevice> ArcMutexChipsetServicesFinalize<T> for FuzzChipsetServicesImpl<'_> {
575    fn finalize(self, dev: &Arc<CloseableMutex<T>>, _name: Arc<str>) {
576        self.vm_chipset.devices.push(dev.clone());
577    }
578}
579
580impl MmioInterceptServices for FuzzChipsetServicesImpl<'_> {
581    fn register_mmio(&self) -> FuzzRegisterMmioIntercept {
582        self.took_mmio.set(true);
583        FuzzRegisterMmioIntercept {
584            dev: self.dev.clone(),
585            map: self.vm_chipset.mmio_ranges.clone(),
586        }
587    }
588
589    fn is_being_used(&self) -> bool {
590        self.took_mmio.get()
591    }
592}
593
594impl PortIoInterceptServices for FuzzChipsetServicesImpl<'_> {
595    fn register_pio(&self) -> FuzzRegisterPortIoIntercept {
596        self.took_pio.set(true);
597        FuzzRegisterPortIoIntercept {
598            dev: self.dev.clone(),
599            map: self.vm_chipset.pio_ranges.clone(),
600        }
601    }
602
603    fn is_being_used(&self) -> bool {
604        self.took_pio.get()
605    }
606}
607
608impl PciConfigSpaceServices for FuzzChipsetServicesImpl<'_> {
609    fn register_static_pci(&mut self, bus: u8, device: u8, function: u8) {
610        self.took_pci.set(true);
611        self.vm_chipset
612            .pci_devices
613            .insert((bus, device, function), self.dev.clone());
614    }
615
616    fn is_being_used(&self) -> bool {
617        self.took_pci.get()
618    }
619}
620
621impl PollDeviceServices for FuzzChipsetServicesImpl<'_> {
622    fn register_poll(&mut self) {
623        self.took_poll.set(true);
624        self.vm_chipset.poll_devices.push(self.dev.clone());
625    }
626
627    fn is_being_used(&self) -> bool {
628        self.took_poll.get()
629    }
630}