diff --git a/src/addr.rs b/src/addr.rs index 4c962e2b4..fcacb3848 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -1,5 +1,6 @@ //! Physical and virtual addresses manipulation +use crate::ops::{CheckedAdd, CheckedSub}; use core::fmt; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -265,6 +266,14 @@ impl AddAssign for VirtAddr { } } +impl CheckedAdd for VirtAddr { + type Output = Self; + #[inline] + fn checked_add(self, rhs: u64) -> Option { + self.0.checked_add(rhs).and_then(from_canonical) + } +} + #[cfg(target_pointer_width = "64")] impl Add for VirtAddr { type Output = Self; @@ -297,6 +306,14 @@ impl SubAssign for VirtAddr { } } +impl CheckedSub for VirtAddr { + type Output = Self; + #[inline] + fn checked_sub(self, rhs: u64) -> Option { + self.0.checked_sub(rhs).and_then(from_canonical) + } +} + #[cfg(target_pointer_width = "64")] impl Sub for VirtAddr { type Output = Self; @@ -322,6 +339,14 @@ impl Sub for VirtAddr { } } +impl CheckedSub for VirtAddr { + type Output = u64; + #[inline] + fn checked_sub(self, rhs: VirtAddr) -> Option { + self.0.checked_sub(rhs.0) + } +} + /// A passed `u64` was not a valid physical address. /// /// This means that bits 52 to 64 were not all null. @@ -479,6 +504,16 @@ impl AddAssign for PhysAddr { } } +impl CheckedAdd for PhysAddr { + type Output = Self; + #[inline] + fn checked_add(self, rhs: u64) -> Option { + self.0 + .checked_add(rhs) + .and_then(|addr| Self::try_new(addr).ok()) + } +} + #[cfg(target_pointer_width = "64")] impl Add for PhysAddr { type Output = Self; @@ -511,6 +546,16 @@ impl SubAssign for PhysAddr { } } +impl CheckedSub for PhysAddr { + type Output = Self; + #[inline] + fn checked_sub(self, rhs: u64) -> Option { + self.0 + .checked_sub(rhs) + .and_then(|addr| Self::try_new(addr).ok()) + } +} + #[cfg(target_pointer_width = "64")] impl Sub for PhysAddr { type Output = Self; @@ -536,6 +581,28 @@ impl Sub for PhysAddr { } } +impl CheckedSub for PhysAddr { + type Output = u64; + #[inline] + fn checked_sub(self, rhs: Self) -> Option { + self.as_u64().checked_sub(rhs.as_u64()) + } +} + +/// Checks whether the address is canonical. + +/// Returns an `Option` if the passed u64 falls within canonical +/// address space. +#[inline] +const fn from_canonical(addr: u64) -> Option { + let trunc = VirtAddr::new_truncate(addr); + if trunc.as_u64() == addr { + Some(trunc) + } else { + None + } +} + /// Align address downwards. /// /// Returns the greatest `x` with alignment `align` so that `x <= addr`. @@ -593,4 +660,38 @@ mod tests { assert_eq!(align_up(0, 2), 0); assert_eq!(align_up(0, 0x8000_0000_0000_0000), 0); } + + #[test] + pub fn checked_operations_only_succeed_on_canonical_addresses() { + let upper_space_upper_bound = 0xffff_ffff_ffff_ffffu64; + let upper_space_lower_bound = 0xffff_8000_0000_0000u64; + let lower_space_upper_bound = 0x7fff_ffff_ffffu64; + let lower_space_lower_bound = 0x0u64; + + assert_eq!( + None, + VirtAddr::new(upper_space_upper_bound).checked_add(1u64) + ); + assert_eq!( + Some(VirtAddr::new(upper_space_lower_bound + 1u64)), + VirtAddr::new(upper_space_lower_bound).checked_add(1u64) + ); + assert_eq!( + None, + VirtAddr::new(upper_space_lower_bound).checked_sub(1u64) + ); + + assert_eq!( + Some(VirtAddr::new(lower_space_upper_bound - 1)), + VirtAddr::new(lower_space_upper_bound).checked_sub(1u64) + ); + assert_eq!( + Some(VirtAddr::new(1)), + VirtAddr::new(lower_space_lower_bound).checked_add(0x1u64) + ); + assert_eq!( + None, + VirtAddr::new(lower_space_lower_bound).checked_sub(1u64) + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 73a6c5061..26df43447 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ pub(crate) mod asm; pub mod addr; pub mod instructions; +pub mod ops; pub mod registers; pub mod structures; diff --git a/src/ops.rs b/src/ops.rs new file mode 100644 index 000000000..bd31e0bb3 --- /dev/null +++ b/src/ops.rs @@ -0,0 +1,81 @@ +//! Additional arithmetic operators for working with addresses. + +/// A checked equivalent to the `+` operator that returns `None` if the +/// operation would result in a panic or a wrapping overflow. Similar to +/// [`std::ops::Add`] an `Output` type _must_ be specified and a `Rhs` type +/// _may_ be specified (defaults to `Self`). +/// +/// We define our own trait rather than use +/// [`num::CheckedAdd`](https://docs.rs/num/latest/num/trait.CheckedAdd.html) +/// due to num's implementation requiring that `Rhs` and `Output` both be `Self`. +/// +/// # Example +/// +/// ``` +/// use x86_64::ops::CheckedAdd; +/// +/// #[derive(Debug, PartialEq)] +/// struct Addr(u64); +/// +/// impl CheckedAdd for Addr { +/// type Output = Self; +/// +/// fn checked_add(self, other: Self) -> Option { +/// self.0.checked_add(other.0).map(Addr) +/// } +/// } +/// +/// assert_eq!(Addr(1).checked_add(Addr(2)), +/// Some(Addr(3))); +/// +/// // Overflowing add +/// assert_eq!(Addr(u64::MAX).checked_add(Addr(1)), +/// None); +/// ``` +pub trait CheckedAdd { + /// The resulting type returned by the `checked_add` operation. + type Output; + + /// Adds two numbers, checking for overflow. If overflow happens, None is returned. + fn checked_add(self, rhs: Rhs) -> Option; +} + +/// A checked equivalent to the `-` operator that returns `None` if the +/// operation would result in a panic or a wrapping underflow. Similar to +/// [`std::ops::Sub`] an `Output` type _must_ be specified and a `Rhs` type +/// _may_ be specified (defaults to `Self`). +/// +/// We define our own trait rather than use +/// [`num::CheckedSub`](https://docs.rs/num/latest/num/trait.CheckedSub.html) +/// due to num's implementation requiring that `Rhs` and `Output` both be `Self`. +/// +/// # Example +/// +/// ``` +/// use x86_64::ops::CheckedSub; +/// +/// #[derive(Debug, PartialEq)] +/// struct Addr(u64); +/// +/// impl CheckedSub for Addr { +/// type Output = Self; +/// +/// fn checked_sub(self, other: Self) -> Option { +/// self.0.checked_sub(other.0).map(Addr) +/// } +/// } +/// +/// assert_eq!(Addr(3).checked_sub(Addr(2)), +/// Some(Addr(1))); +/// +/// // Undeflowing sub +/// assert_eq!(Addr(u64::MIN).checked_sub(Addr(1)), +/// None); +/// ``` +pub trait CheckedSub { + /// The resulting type returned by the `checked_sub` operation. + type Output; + + /// Subtracts two numbers, checking for underflow. If underflow happens, None is returned. + fn checked_sub(self, rhs: Rhs) -> Option; +} diff --git a/src/structures/paging/frame.rs b/src/structures/paging/frame.rs index 63c84f18e..28cad4401 100644 --- a/src/structures/paging/frame.rs +++ b/src/structures/paging/frame.rs @@ -1,6 +1,7 @@ //! Abstractions for default-sized and huge physical memory frames. use super::page::AddressNotAligned; +use crate::ops::{CheckedAdd, CheckedSub}; use crate::structures::paging::page::{PageSize, Size4KiB}; use crate::PhysAddr; use core::fmt; @@ -16,7 +17,7 @@ pub struct PhysFrame { } impl PhysFrame { - /// Returns the frame that starts at the given virtual address. + /// Returns the frame that starts at the given physical address. /// /// Returns an error if the address is not correctly aligned (i.e. is not a valid frame start). #[inline] @@ -30,7 +31,7 @@ impl PhysFrame { } const_fn! { - /// Returns the frame that starts at the given virtual address. + /// Returns the frame that starts at the given physical address. /// /// ## Safety /// @@ -111,6 +112,44 @@ impl AddAssign for PhysFrame { } } +impl CheckedAdd for PhysFrame { + type Output = Self; + + /// Adds a number of frames to the left-hand side, checking for overflow. + /// A new `PhysFrame` is returned represented by the frame with a start + /// address equal to the sum of the left-hand side frame's start address + /// and the `rhs` * `PageSize`. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedAdd; + /// use x86_64::addr::PhysAddr; + /// use x86_64::structures::paging::{ + /// frame::PhysFrame, + /// page::{PageSize, Size4KiB} + /// }; + /// + /// let start_addr_a = 0x0; + /// let phys_frame_a: PhysFrame = PhysFrame::from_start_address(PhysAddr::new(start_addr_a)).unwrap(); + /// let start_addr_b = PhysAddr::new(Size4KiB::SIZE); + /// let phys_frame_b: PhysFrame = PhysFrame::from_start_address(start_addr_b).unwrap(); + /// + /// assert_eq!(Some(phys_frame_b), phys_frame_a.checked_add(1u64)); + /// assert_eq!(None, phys_frame_a.checked_add(u64::MAX)); + /// ``` + #[inline] + fn checked_add(self, rhs: u64) -> Option { + let phys_addr_rhs = rhs + .checked_mul(S::SIZE) + .and_then(|addr| PhysAddr::try_new(addr).ok())?; + + self.start_address() + .checked_add(phys_addr_rhs.as_u64()) + .and_then(|addr| PhysFrame::from_start_address(addr).ok()) + } +} + impl Sub for PhysFrame { type Output = Self; #[inline] @@ -126,6 +165,44 @@ impl SubAssign for PhysFrame { } } +impl CheckedSub for PhysFrame { + type Output = Self; + + /// Subtracts a number of frames from the left-hand side, checking for + /// underflow. A new frame is returned represented by the frame with a + /// start address equal to the difference of the left-hand side frame's and + /// the `rhs` * `PageSize`. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedSub; + /// use x86_64::addr::PhysAddr; + /// use x86_64::structures::paging::{ + /// frame::PhysFrame, + /// page::{PageSize, Size4KiB} + /// }; + /// + /// let start_addr_a = 0x0; + /// let phys_frame_a: PhysFrame = PhysFrame::from_start_address(PhysAddr::new(start_addr_a)).unwrap(); + /// let start_addr_b = PhysAddr::new(start_addr_a + Size4KiB::SIZE); + /// let phys_frame_b: PhysFrame = PhysFrame::from_start_address(start_addr_b).unwrap(); + /// + /// assert_eq!(Some(phys_frame_a), phys_frame_b.checked_sub(1u64)); + /// assert_eq!(None, phys_frame_a.checked_sub(1u64)); + /// ``` + #[inline] + fn checked_sub(self, rhs: u64) -> Option { + let phys_addr_rhs = rhs + .checked_mul(S::SIZE) + .and_then(|addr| PhysAddr::try_new(addr).ok())?; + + self.start_address() + .checked_sub(phys_addr_rhs.as_u64()) + .and_then(|addr| PhysFrame::from_start_address(addr).ok()) + } +} + impl Sub> for PhysFrame { type Output = u64; #[inline] @@ -134,6 +211,40 @@ impl Sub> for PhysFrame { } } +impl CheckedSub> for PhysFrame { + type Output = u64; + + /// Subtracts the first address of two frames, checking for underflow. If + /// underflow occurs, None is returned. Otherwise, the difference in frames + /// is returned. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedSub; + /// use x86_64::addr::PhysAddr; + /// use x86_64::structures::paging::{ + /// frame::PhysFrame, + /// page::{PageSize, Size4KiB} + /// }; + /// + /// let start_addr_a = PhysAddr::zero(); + /// let phys_frame_a: PhysFrame = PhysFrame::from_start_address(start_addr_a).unwrap(); + /// let start_addr_b = PhysAddr::new(0x0 + Size4KiB::SIZE); + /// let phys_frame_b: PhysFrame = PhysFrame::from_start_address(start_addr_b).unwrap(); + /// + /// + /// assert_eq!(Some(1), phys_frame_b.checked_sub(phys_frame_a)); + /// assert_eq!(None, phys_frame_a.checked_sub(phys_frame_b)); + /// ``` + #[inline] + fn checked_sub(self, rhs: PhysFrame) -> Option { + self.start_address() + .checked_sub(rhs.start_address()) + .map(|offset| offset / S::SIZE) + } +} + /// An range of physical memory frames, exclusive the upper bound. #[derive(Clone, Copy, PartialEq, Eq)] #[repr(C)] diff --git a/src/structures/paging/page.rs b/src/structures/paging/page.rs index 90e467bfc..c4101423b 100644 --- a/src/structures/paging/page.rs +++ b/src/structures/paging/page.rs @@ -1,5 +1,6 @@ //! Abstractions for default-sized and huge virtual memory pages. +use crate::ops::{CheckedAdd, CheckedSub}; use crate::structures::paging::page_table::PageTableLevel; use crate::structures::paging::PageTableIndex; use crate::VirtAddr; @@ -251,6 +252,43 @@ impl AddAssign for Page { } } +impl CheckedAdd for Page { + type Output = Self; + + /// Adds a number of pages to the left-hand side, checking for overflow. + /// A new `Page` is returned represented by the `Page` with a start address + /// equal to the sum of the left-hand side page's start address and the + /// `rhs` * `PageSize`. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedAdd; + /// use x86_64::addr::VirtAddr; + /// use x86_64::structures::paging::{ + /// page::{Page, PageSize, Size4KiB} + /// }; + /// + /// let start_addr_a = VirtAddr::zero(); + /// let page_a: Page = Page::containing_address(start_addr_a); + /// let start_addr_b = VirtAddr::new(0x0 + Size4KiB::SIZE); + /// let page_b: Page = Page::containing_address(start_addr_b); + /// + /// assert_eq!(Some(page_b), page_a.checked_add(1u64)); + /// assert_eq!(None, page_a.checked_add(u64::MAX)); + /// ``` + #[inline] + fn checked_add(self, rhs: u64) -> Option { + let virt_addr_rhs = rhs + .checked_mul(S::SIZE) + .and_then(|addr| VirtAddr::try_new(addr).ok())?; + + self.start_address() + .checked_add(virt_addr_rhs.as_u64()) + .and_then(|addr| Page::from_start_address(addr).ok()) + } +} + impl Sub for Page { type Output = Self; #[inline] @@ -266,6 +304,43 @@ impl SubAssign for Page { } } +impl CheckedSub for Page { + type Output = Self; + + /// Subtracts a number of pages from the left-hand side, checking for underflow. + /// A new `Page` is returned represented by the `Page` with a start address + /// equal to the difference of the left-hand side page's start address and the + /// `rhs` * `PageSize`. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedSub; + /// use x86_64::addr::VirtAddr; + /// use x86_64::structures::paging::{ + /// page::{Page, PageSize, Size4KiB} + /// }; + /// + /// let start_addr_a = VirtAddr::zero(); + /// let page_a: Page = Page::containing_address(start_addr_a); + /// let start_addr_b = VirtAddr::new(0x0 + Size4KiB::SIZE); + /// let page_b: Page = Page::containing_address(start_addr_b); + /// + /// assert_eq!(Some(page_a), page_b.checked_sub(1u64)); + /// assert_eq!(None, page_a.checked_sub(1u64)); + /// ``` + #[inline] + fn checked_sub(self, rhs: u64) -> Option { + let virt_addr_rhs = rhs + .checked_mul(S::SIZE) + .and_then(|addr| VirtAddr::try_new(addr).ok())?; + + self.start_address() + .checked_sub(virt_addr_rhs.as_u64()) + .and_then(|addr| Page::from_start_address(addr).ok()) + } +} + impl Sub for Page { type Output = u64; #[inline] @@ -274,6 +349,37 @@ impl Sub for Page { } } +impl CheckedSub for Page { + type Output = u64; + + /// Subtracts the first address of two pages, checking for underflow. If + /// underflow occurs, None is returned. Otherwise, the difference in pages + /// is returned. + /// + /// # Example + /// + /// ``` + /// use x86_64::ops::CheckedSub; + /// use x86_64::addr::VirtAddr; + /// use x86_64::structures::paging::{Page, PageSize, Size4KiB}; + /// + /// let start_addr_a = VirtAddr::zero(); + /// let page_a: Page = Page::containing_address(start_addr_a); + /// let start_addr_b = VirtAddr::new(0x0 + Size4KiB::SIZE); + /// let page_b: Page = Page::containing_address(start_addr_b); + /// + /// + /// assert_eq!(Some(1), page_b.checked_sub(page_a)); + /// assert_eq!(None, page_a.checked_sub(page_b)); + /// ``` + #[inline] + fn checked_sub(self, rhs: Self) -> Option { + self.start_address() + .checked_sub(rhs.start_address()) + .map(|offset| offset / S::SIZE) + } +} + /// A range of pages with exclusive upper bound. #[derive(Clone, Copy, PartialEq, Eq)] #[repr(C)]