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