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