lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20251009-bounded_ints-v2-2-ff3d7fee3ffd@nvidia.com>
Date: Thu, 09 Oct 2025 21:37:09 +0900
From: Alexandre Courbot <acourbot@...dia.com>
To: Danilo Krummrich <dakr@...nel.org>, 
 Joel Fernandes <joelagnelf@...dia.com>, Yury Norov <yury.norov@...il.com>, 
 Jesung Yang <y.j3ms.n@...il.com>, Miguel Ojeda <ojeda@...nel.org>, 
 Alex Gaynor <alex.gaynor@...il.com>, Boqun Feng <boqun.feng@...il.com>, 
 Gary Guo <gary@...yguo.net>, 
 Björn Roy Baron <bjorn3_gh@...tonmail.com>, 
 Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>, 
 Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>
Cc: nouveau@...ts.freedesktop.org, linux-kernel@...r.kernel.org, 
 rust-for-linux@...r.kernel.org, Alexandre Courbot <acourbot@...dia.com>
Subject: [PATCH RFC v2 2/3] rust: kernel: add bounded integer types

Add the BoundedInt type, which restricts the number of bits allowed to
be used in a given integer value. This is useful to carry guarantees
when setting bitfields.

Alongside this type, many `From` and `TryFrom` implementations are
provided to reduce friction when using with regular integer types. Proxy
implementations of common integer traits are also provided.

Signed-off-by: Alexandre Courbot <acourbot@...dia.com>
---
 rust/kernel/lib.rs |   1 +
 rust/kernel/num.rs | 499 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 500 insertions(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index fcffc3988a90..21c1f452ee6a 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -101,6 +101,7 @@
 pub mod mm;
 #[cfg(CONFIG_NET)]
 pub mod net;
+pub mod num;
 pub mod of;
 #[cfg(CONFIG_PM_OPP)]
 pub mod opp;
diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs
new file mode 100644
index 000000000000..b2aad95ce51c
--- /dev/null
+++ b/rust/kernel/num.rs
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Numerical types for the kernel.
+
+use kernel::prelude::*;
+
+/// Integer type for which only the bits `0..NUM_BITS` are valid.
+///
+/// # Invariants
+///
+/// Stored values are represented with at most `NUM_BITS` bits.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, Default, Hash)]
+pub struct BoundedInt<T, const NUM_BITS: u32>(T);
+
+/// Returns `true` if `$value` can be represented with at most `$NUM_BITS` on `$type`.
+macro_rules! is_in_bounds {
+    ($value:expr, $type:ty, $num_bits:expr) => {{
+        let v = $value;
+        v & <$type as Boundable<NUM_BITS>>::MASK == v
+    }};
+}
+
+/// Trait for primitive integer types that can be used with `BoundedInt`.
+pub trait Boundable<const NUM_BITS: u32>
+where
+    Self: Sized + Copy + core::ops::BitAnd<Output = Self> + core::cmp::PartialEq,
+    Self: TryInto<u8> + TryInto<u16> + TryInto<u32> + TryInto<u64>,
+{
+    /// Mask of the valid bits for this type.
+    const MASK: Self;
+
+    /// Returns `true` if `value` can be represented with at most `NUM_BITS`.
+    ///
+    /// TODO: post-RFC: replace this with a left-shift followed by right-shift operation. This will
+    /// allow us to handle signed values as well.
+    fn is_in_bounds(value: Self) -> bool {
+        is_in_bounds!(value, Self, NUM_BITS)
+    }
+}
+
+impl<const NUM_BITS: u32> Boundable<NUM_BITS> for u8 {
+    const MASK: u8 = crate::bits::genmask_u8(0..=(NUM_BITS - 1));
+}
+
+impl<const NUM_BITS: u32> Boundable<NUM_BITS> for u16 {
+    const MASK: u16 = crate::bits::genmask_u16(0..=(NUM_BITS - 1));
+}
+
+impl<const NUM_BITS: u32> Boundable<NUM_BITS> for u32 {
+    const MASK: u32 = crate::bits::genmask_u32(0..=(NUM_BITS - 1));
+}
+
+impl<const NUM_BITS: u32> Boundable<NUM_BITS> for u64 {
+    const MASK: u64 = crate::bits::genmask_u64(0..=(NUM_BITS - 1));
+}
+
+impl<T, const NUM_BITS: u32> BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+{
+    /// Checks that `value` is valid for this type at compile-time and build a new value.
+    ///
+    /// This relies on [`build_assert!`] to perform validation at compile-time. If `value` cannot
+    /// be inferred to be in bounds at compile-time, use the fallible [`Self::try_new`] instead.
+    ///
+    /// When possible, use one of the `new_const` methods instead of this method as it statically
+    /// validates `value` instead of relying on the compiler's optimizations.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// # fn some_number() -> u32 { 0xffffffff }
+    ///
+    /// assert_eq!(BoundedInt::<u8, 1>::new(1).get(), 1);
+    /// assert_eq!(BoundedInt::<u16, 8>::new(0xff).get(), 0xff);
+    ///
+    /// // Triggers a build error as `0x1ff` doesn't fit into 8 bits.
+    /// // assert_eq!(BoundedInt::<u32, 8>::new(0x1ff).get(), 0x1ff);
+    ///
+    /// let v: u32 = some_number();
+    /// // Triggers a build error as `v` cannot be asserted to fit within 4 bits...
+    /// // let _ = BoundedInt::<u32, 4>::new(v);
+    /// // ... but this works as the compiler can assert the range from the mask.
+    /// let _ = BoundedInt::<u32, 4>::new(v & 0xf);
+    /// ```
+    pub fn new(value: T) -> Self {
+        crate::build_assert!(
+            T::is_in_bounds(value),
+            "Provided parameter is larger than maximal supported value"
+        );
+
+        Self(value)
+    }
+
+    /// Attempts to convert `value` into a value bounded by `NUM_BITS`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// assert_eq!(BoundedInt::<u8, 1>::try_new(1).map(|v| v.get()), Ok(1));
+    /// assert_eq!(BoundedInt::<u16, 8>::try_new(0xff).map(|v| v.get()), Ok(0xff));
+    ///
+    /// // `0x1ff` doesn't fit into 8 bits.
+    /// assert_eq!(BoundedInt::<u32, 8>::try_new(0x1ff), Err(EOVERFLOW));
+    /// ```
+    pub fn try_new(value: T) -> Result<Self> {
+        if !T::is_in_bounds(value) {
+            Err(EOVERFLOW)
+        } else {
+            Ok(Self(value))
+        }
+    }
+
+    /// Returns the contained value as a primitive type.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// let v = BoundedInt::<u32, 4>::new_const::<7>();
+    /// assert_eq!(v.get(), 7u32);
+    /// ```
+    pub fn get(self) -> T {
+        if !T::is_in_bounds(self.0) {
+            // SAFETY: Per the invariants, `self.0` cannot have bits set outside of `MASK`, so
+            // this block will
+            // never be reached.
+            unsafe { core::hint::unreachable_unchecked() }
+        }
+        self.0
+    }
+
+    /// Increase the number of bits usable for `self`.
+    ///
+    /// This operation cannot fail.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// let v = BoundedInt::<u32, 4>::new_const::<7>();
+    /// let larger_v = v.enlarge::<12>();
+    /// // The contained values are equal even though `larger_v` has a bigger capacity.
+    /// assert_eq!(larger_v, v);
+    /// ```
+    pub const fn enlarge<const NEW_NUM_BITS: u32>(self) -> BoundedInt<T, NEW_NUM_BITS>
+    where
+        T: Boundable<NEW_NUM_BITS>,
+        T: Copy,
+    {
+        build_assert!(NEW_NUM_BITS >= NUM_BITS);
+
+        // INVARIANT: the value did fit within `NUM_BITS`, so it will all the more fit within
+        // `NEW_NUM_BITS` which is larger.
+        BoundedInt(self.0)
+    }
+
+    /// Shrink the number of bits usable for `self`.
+    ///
+    /// Returns `EOVERFLOW` if the value of `self` cannot be represented within `NEW_NUM_BITS`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// let v = BoundedInt::<u32, 12>::new_const::<7>();
+    /// let smaller_v = v.shrink::<4>()?;
+    /// // The contained values are equal even though `smaller_v` has a smaller capacity.
+    /// assert_eq!(smaller_v, v);
+    ///
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn shrink<const NEW_NUM_BITS: u32>(self) -> Result<BoundedInt<T, NEW_NUM_BITS>>
+    where
+        T: Boundable<NEW_NUM_BITS>,
+        T: Copy,
+    {
+        BoundedInt::<T, NEW_NUM_BITS>::try_new(self.get())
+    }
+
+    /// Casts `self` into a `BoundedInt` using a different storage type, but using the same
+    /// number of bits for representation.
+    ///
+    /// This method cannot fail as the number of bits used for representation doesn't change.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::num::BoundedInt;
+    ///
+    /// let v = BoundedInt::<u32, 4>::new_const::<7>();
+    /// let smaller_v: BoundedInt<u8, _> = v.cast();
+    /// // The contained values are equal even though `smaller_v` has a smaller storage type.
+    /// assert_eq!(u32::from(smaller_v.get()), v.get());
+    /// ```
+    pub fn cast<U>(self) -> BoundedInt<U, NUM_BITS>
+    where
+        U: TryFrom<T> + Boundable<NUM_BITS>,
+    {
+        // SAFETY: the contained value is represented using `NUM_BITS`, and `U` can be bounded to
+        // `NUM_BITS`, hence the conversion cannot fail.
+        let value = unsafe { U::try_from(self.0).unwrap_unchecked() };
+
+        // INVARIANT: although the storage type has changed, the value is still represented within
+        // `NUM_BITS`.
+        BoundedInt(value)
+    }
+}
+
+/// Validating the value as a const expression cannot be done as a regular method, as the
+/// arithmetic expressions we rely on to check the bounds are not const. Thus, implement
+/// `new_const` using a macro.
+macro_rules! impl_const_new {
+    ($($type:ty)*) => {
+        $(
+        impl<const NUM_BITS: u32> BoundedInt<$type, NUM_BITS> {
+            /// Creates a bounded value for `VALUE`, statically validated.
+            ///
+            /// This method should be used instead of [`Self::new`] when the value is a constant
+            /// expression.
+            ///
+            /// # Examples
+            /// ```
+            /// use kernel::num::BoundedInt;
+            ///
+            #[doc = ::core::concat!(
+                "let v = BoundedInt::<",
+                ::core::stringify!($type),
+                ", 4>::new_const::<7>();")]
+            /// assert_eq!(v.get(), 7);
+            /// ```
+            pub const fn new_const<const VALUE: $type>() -> Self {
+                build_assert!(is_in_bounds!(VALUE, $type, NUM_BITS));
+
+                Self(VALUE)
+            }
+        }
+        )*
+    };
+}
+
+impl_const_new!(u8 u16 u32 u64);
+
+/// Declares a new `$trait` and implements it for all bounded types represented using `$num_bits`.
+///
+/// This is used to declare properties as traits that we can use for later implementations.
+macro_rules! impl_size_rule {
+    ($trait:ident, $($num_bits:literal)*) => {
+        trait $trait {}
+
+        $(
+        impl<T> $trait for BoundedInt<T, $num_bits> where T: Boundable<$num_bits> {}
+        )*
+    };
+}
+
+// Bounds that are larger than a `u64`.
+impl_size_rule!(LargerThanU64, 64);
+
+// Bounds that are larger than a `u32`.
+impl_size_rule!(LargerThanU32,
+    32 33 34 35 36 37 38 39
+    40 41 42 43 44 45 46 47
+    48 49 50 51 52 53 54 55
+    56 57 58 59 60 61 62 63
+);
+// Anything larger than `u64` is also larger than `u32`.
+impl<T> LargerThanU32 for T where T: LargerThanU64 {}
+
+// Bounds that are larger than a `u16`.
+impl_size_rule!(LargerThanU16,
+    16 17 18 19 20 21 22 23
+    24 25 26 27 28 29 30 31
+);
+// Anything larger than `u32` is also larger than `u16`.
+impl<T> LargerThanU16 for T where T: LargerThanU32 {}
+
+// Bounds that are larger than a `u8`.
+impl_size_rule!(LargerThanU8, 8 9 10 11 12 13 14 15);
+// Anything larger than `u16` is also larger than `u8`.
+impl<T> LargerThanU8 for T where T: LargerThanU16 {}
+
+// Bounds that are larger than a boolean.
+impl_size_rule!(LargerThanBool, 1 2 3 4 5 6 7);
+// Anything larger than `u8` is also larger than `bool`.
+impl<T> LargerThanBool for T where T: LargerThanU8 {}
+
+/// Generates `From` implementations from a primitive type into a bounded integer that is
+/// guaranteed to being able to contain it.
+macro_rules! impl_from_primitive {
+    ($($type:ty => $trait:ident),*) => {
+        $(
+        impl<T, const NUM_BITS: u32> From<$type> for BoundedInt<T, NUM_BITS>
+        where
+            Self: $trait,
+            T: From<$type>,
+        {
+            fn from(value: $type) -> Self {
+                Self(T::from(value))
+            }
+        }
+        )*
+    }
+}
+
+impl_from_primitive!(
+    bool => LargerThanBool,
+    u8 => LargerThanU8,
+    u16 => LargerThanU16,
+    u32 => LargerThanU32,
+    u64 => LargerThanU64
+);
+
+impl_size_rule!(FitsIntoBool, 1);
+
+impl_size_rule!(FitsIntoU8, 2 3 4 5 6 7 8);
+
+// Anything that fits into a `bool` also fits into a `u8`.
+impl<T> FitsIntoU8 for T where T: FitsIntoBool {}
+
+impl_size_rule!(FitsIntoU16, 9 10 11 12 13 14 15 16);
+
+// Anything that fits into a `u8` also fits into a `u16`.
+impl<T> FitsIntoU16 for T where T: FitsIntoU8 {}
+
+impl_size_rule!(FitsIntoU32,
+    17 18 19 20 21 22 23 24
+    25 26 27 28 29 30 31 32
+);
+
+// Anything that fits into a `u16` also fits into a `u32`.
+impl<T> FitsIntoU32 for T where T: FitsIntoU16 {}
+
+impl_size_rule!(FitsIntoU64,
+    33 34 35 36 37 38 39 40
+    41 42 43 44 45 46 47 48
+    49 50 51 52 53 54 55 56
+    57 58 59 60 61 62 63 64
+);
+
+// Anything that fits into a `u32` also fits into a `u64`.
+impl<T> FitsIntoU64 for T where T: FitsIntoU32 {}
+
+/// Generates `From` implementations from a bounded integer into a primitive type that is
+/// guaranteed to being able to contain it.
+macro_rules! impl_into_primitive {
+    ($($trait:ident => $type:ty),*) => {
+        $(
+        impl<T, const NUM_BITS: u32> From<BoundedInt<T, NUM_BITS>> for $type
+        where
+            T: Boundable<NUM_BITS>,
+            BoundedInt<T, NUM_BITS>: $trait
+        {
+            fn from(value: BoundedInt<T, NUM_BITS>) -> Self {
+                // SAFETY: per the `BoundedInt` invariants, less than 8 bits are used to the conversion
+                // cannot fail.
+                unsafe { value.get().try_into().unwrap_unchecked() }
+            }
+        }
+        )*
+    }
+}
+
+impl_into_primitive!(
+    FitsIntoU8 => u8,
+    FitsIntoU16 => u16,
+    FitsIntoU32 => u32,
+    FitsIntoU64 => u64
+);
+
+// Conversion to boolean must be handled separately as it does not have `TryFrom` implementation
+// from integers.
+impl<T> From<BoundedInt<T, 1>> for bool
+where
+    T: Boundable<1>,
+    BoundedInt<T, 1>: FitsIntoBool,
+    T: PartialEq + Zeroable,
+{
+    fn from(value: BoundedInt<T, 1>) -> Self {
+        value.get() != Zeroable::zeroed()
+    }
+}
+
+/// Trait similar to `TryInto` to avoid conflicting implementations errors.
+pub trait TryIntoBounded<T: Boundable<NUM_BITS>, const NUM_BITS: u32> {
+    /// Attempts to convert `self` into a value bounded by `NUM_BITS`.
+    fn try_into(self) -> Result<BoundedInt<T, NUM_BITS>>;
+}
+
+/// Any value can be attempted to be converted into a bounded integer of any size.
+impl<T, U, const NUM_BITS: u32> TryIntoBounded<T, NUM_BITS> for U
+where
+    T: Boundable<NUM_BITS>,
+    U: TryInto<T>,
+{
+    fn try_into(self) -> Result<BoundedInt<T, NUM_BITS>> {
+        self.try_into()
+            .map_err(|_| EOVERFLOW)
+            .and_then(BoundedInt::try_new)
+    }
+}
+
+/// `BoundedInts` can be compared if their respective storage types can be.
+impl<T, U, const NUM_BITS: u32, const NUM_BITS_U: u32> PartialEq<BoundedInt<U, NUM_BITS_U>>
+    for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    U: Boundable<NUM_BITS_U>,
+    T: PartialEq<U>,
+{
+    fn eq(&self, other: &BoundedInt<U, NUM_BITS_U>) -> bool {
+        self.get() == other.get()
+    }
+}
+
+impl<T, const NUM_BITS: u32> Eq for BoundedInt<T, NUM_BITS> where T: Boundable<NUM_BITS> {}
+
+/// `BoundedInts` can be ordered if their respective storage types can be.
+impl<T, U, const NUM_BITS: u32, const NUM_BITS_U: u32> PartialOrd<BoundedInt<U, NUM_BITS_U>>
+    for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    U: Boundable<NUM_BITS_U>,
+    T: PartialOrd<U>,
+{
+    fn partial_cmp(&self, other: &BoundedInt<U, NUM_BITS_U>) -> Option<core::cmp::Ordering> {
+        self.get().partial_cmp(&other.get())
+    }
+}
+
+impl<T, const NUM_BITS: u32> Ord for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: Ord,
+{
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        self.get().cmp(&other.get())
+    }
+}
+
+/// Allow comparison with non-bounded values.
+impl<T, const NUM_BITS: u32> PartialEq<T> for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: PartialEq,
+{
+    fn eq(&self, other: &T) -> bool {
+        self.get() == *other
+    }
+}
+
+/// Allow ordering with non-bounded values.
+impl<T, const NUM_BITS: u32> PartialOrd<T> for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: PartialOrd,
+{
+    fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> {
+        self.get().partial_cmp(other)
+    }
+}
+
+impl<T, const NUM_BITS: u32> core::fmt::Display for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: core::fmt::Display,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl<T, const NUM_BITS: u32> core::fmt::LowerHex for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: core::fmt::LowerHex,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl<T, const NUM_BITS: u32> core::fmt::UpperHex for BoundedInt<T, NUM_BITS>
+where
+    T: Boundable<NUM_BITS>,
+    T: core::fmt::UpperHex,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        self.0.fmt(f)
+    }
+}

-- 
2.51.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ