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-next>] [day] [month] [year] [list]
Message-Id: <20250125-add-bitfield-options-macro-v1-1-af6ae93686cb@protonmail.com>
Date: Sat, 25 Jan 2025 19:09:14 +0100
From: GTimothy via B4 Relay <devnull+gtimothy-dev.protonmail.com@...nel.org>
To: Miguel Ojeda <ojeda@...nel.org>, Alex Gaynor <alex.gaynor@...il.com>
Cc: Boqun Feng <boqun.feng@...il.com>, Gary Guo <gary@...yguo.net>, 
 Björn Roy Baron <bjorn3_gh@...tonmail.com>, 
 Benno Lossin <benno.lossin@...ton.me>, 
 Andreas Hindborg <a.hindborg@...nel.org>, Alice Ryhl <aliceryhl@...gle.com>, 
 Trevor Gross <tmgross@...ch.edu>, linux-kernel@...r.kernel.org, 
 rust-for-linux@...r.kernel.org, GTimothy <gtimothy-dev@...tonmail.com>
Subject: [PATCH RFC WIP] bitflag_options macro_rules implementation

From: GTimothy <gtimothy-dev@...tonmail.com>

- implement a macro to generate a bitfield wrapper with macro-specified
  methods to set and unset macro-specified flags.
- introduce a Bitfield trait to access the field

The macro also generates a `Default` implementation for the wrapper
type, which instanciates a wrapped bitfield as if created with all
methods called with `false`.

The macro also generates a `TryFrom<wrapped_bitfield>` implementation
for the wrapper that prevents instanciating a bitfield wrapper with an
invalid field.

Signed-off-by: GTimothy <gtimothy-dev@...tonmail.com>
---
Hello all,

There was some discussion about handling bitfield C enums and
bitfield-like C enums in a safe manner in the "Best way to handle
enum/flags situation"[1] zulip discussion about the HRTIMER_MODE enum in
`include/linux/hrtimer.h`.

I will define *bitfield* as a bit set where
 - different flags set different bits (no overlap),
 - all flags are additive (there are no invalid combination of flags),
 - all flags are optional.

I will define *bitfield-like* in the context of the HRTIMER_MODE
bitfield-LIKE C enum as a bit set where:
 - different BASE flags set different bits (no overlap) but there are
   aliases for some flag combinations, and some aliases are equal to a
   base case (HRTIMER_MODE_ABS_PINNED == HRTIMER_MODE_PINNE because
   HRTIMER_MODE_ABS==0x00),
 - not all flags are additive, there may invalid combination of flags:
   - HRTIMER_MODE_SOFT and HRTIMER_MODE_HARD (if I am not mistaken)
   - HRTIMER_MODE_ABS and HRTIMER_MODE_REL (though this one is mitigated
     by the fact that ABS is 0x00 therefore ABS|REL==REL)

Issues:
As the case of HRTIMER_MODE shows, sometimes the the valid and invalid
combinations are not semantically incoded in the structure. In order to
manipulate such a field safely in Rust, we can wrap it in a type
that prevents the user from reaching an invalid bitfield state before we
use it in C code.

Additionally, 'well formed' bitfield C enums are not marked as such,
distinguishing them from normal C enums in a way that would allow for a
specific bindgen solution.

Possible solutions:
 - C centered solution: refactor bitfield enums in the kernel to
   respects the first definition and 'annotate them' in a way that would
   allow a custom bindgen conversion to a Rust bitfield wrapper
 - Rust cented solution: define a rust macro that builds a bitfield
   wrapper implementing flag setting methods like `std::fs::OpenOptions`
   by taking as input some semantic information.
   Call this macro by hand with the proper semantic information to use
   that C enum in Rust.

Here is a Rust macro solution that generate such a wrapper:
1. It generates methods like OpenOptions' to set/unset flags.
2. It handle invalid flag pairs by unsetting the one before setting the
   other.
3. It permits flags that set/unset more than one bit at a time (this may
   be a mistake, but in case the C bitfield enum does it too).
4. It implements the Default trait.
5. It implements the TryFrom<field_type> trait to attempt instanciation
   from a passed bitfield.

I am requesting comments, any issues/suggestions? I have some questions
myself:
 - would this better be placed in rust/macro/ or rust/kernel/?
 - should the macro take in a :
   ```
   <#optional_attributes>
   <visibility> <wrapper_name>(<field_type>);
   ```
   instead of the current `name:<wrapper_name>` and `type:<field_type>`
   entries?
   It might be better since it allows one to decorate the wrapper with
   desired attributes.
 - 3. may be a mistake. We could handle only single-bit flags and pass
   the bit offset instead to the macro for each flag and its optional
   counterflag.
 - I created a Bitfield trait providing a bits() function, but the
   bits() function could also simply be part of the wrapper's impl.
   Thoughts?

Thanks in advance for any feedback,
Timothy

[1]
https://rust-for-linux.zulipchat.com/#narrow/channel/291565-Help/topic/Best.20way.20to.20handle.20enum.2Fflags.20situation
---
 rust/kernel/bitfield.rs | 254 ++++++++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs      |   1 +
 2 files changed, 255 insertions(+)

diff --git a/rust/kernel/bitfield.rs b/rust/kernel/bitfield.rs
new file mode 100644
index 0000000000000000000000000000000000000000..34e31b7c54e793ad1d5e253fd499736bfb629876
--- /dev/null
+++ b/rust/kernel/bitfield.rs
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Trait and macro to build bitfield wrappers.
+//!
+//! A trait and [kernel::bitfield_options] macro to build bitfield wrappers with
+//! [std::fs::OpenOptions](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)-like methods to
+//! set bits and _try to ensure an always-valid state_.
+
+/// A trait associating a bitfield type to a wrapper type to allow for shared trait implementations
+/// and bitfield retrieval.
+pub trait BitField {
+    /// The bitfield type, usually [u8] or [u32]
+    type Bits;
+
+    /// returns the bits stored by the wrapper.
+    fn bits(&self) -> Self::Bits;
+}
+
+/// concatenate sequences using a comma separator. Uses a a `, and` (oxford comma) separation
+/// instead between the last two items.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! concat_with_oxford_comma {
+    ($last:expr) => (concat!("and ", $last));
+    ($left:expr, $($right:expr),+ ) => (
+        concat!($left, ", ", $crate::concat_with_oxford_comma!($($right),+))
+    )
+}
+
+/// Builds a bitfield wrapper with
+/// [std::fs::OpenOptions](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)-like methods to
+/// set bits.
+///
+/// This wrapper _tries to ensure an always-valid state_, even when created from a bitfield
+/// directly.
+///
+/// The wrapper implements the [BitField] trait.
+///
+/// # argument types
+///   - `name`: the name of the wrapper to build.
+///   - `type`: the bit field type, usually `u32` or `u8`. Must be an int primitive.
+///   - 'options' : array of `{ name:ident, true:expr, false:expr }` options where name is the name
+///     of a method that will set the `true:expr` bit(s) when called with `true` or else the
+///     `false:expr` bit(s). In a standard bitfield, `false:expr` is set to `false:0`, but sometimes
+///     different bit flags designate incompatible alternatives : in these cases, set `false` to
+///     activate the alternative bit(s).
+///
+/// # Examples
+///
+/// Call the macro to create the bitflag wrapper.
+///
+/// Here we create a wrapper that allows to set `RED`, `BLUE`, `GREEN` bits independently,
+/// and additionnaly either the `BIG` bit or the `SMALL` bit.
+///
+/// `BIG` and `SMALL` are incompatible alternatives.
+///
+/// ```rust
+/// use kernel::bitfield_options;
+///
+/// const BIG: u32 = 16;
+/// const SMALL: u32 = 1;
+///
+/// const RED: u32 = 2;
+/// const GREEN: u32 = 4;
+/// const BLUE: u32 = 8;
+///
+/// bitfield_options! {
+///     name: SizeAndColors,
+///     type: u32,
+///     options: [
+///         {name:big, true:BIG, false:SMALL},
+///         {name:red, true:RED, false:0u32},
+///         {name:blue, true:BLUE, false:0u32},
+///         {name:green, true:GREEN, false:0u32}
+///     ]
+/// };
+/// ```
+///
+/// The `SizeAndColor` type is now available in this scope.
+///
+/// It implements [Default]: all options set to false.
+///
+///
+/// ```rust
+/// # use kernel::bitfield_options;
+/// # use kernel::bitfield::BitField; // to have access to the bits() method
+/// #
+/// # const BIG: u32 = 16;
+/// # const SMALL: u32 = 1;
+/// #
+/// # const RED: u32 = 2;
+/// # const GREEN: u32 = 4;
+/// # const BLUE: u32 = 8;
+/// #
+/// # bitfield_options! {
+/// #     name: SizeAndColors,
+/// #     type: u32,
+/// #     options: [
+/// #         {name:big, true:BIG, false:SMALL},
+/// #         {name:red, true:RED, false:0u32},
+/// #         {name:blue, true:BLUE, false:0u32},
+/// #         {name:green, true:GREEN, false:0u32}
+/// #     ]
+/// # };
+/// let sac = SizeAndColors::default();
+///
+/// // the default state, with all the options set to false should be the field with only the
+/// // SMALL bit(s) set
+/// assert_eq!(
+///     sac.bits(),
+///     SMALL | 0u32 | 0u32 | 0u32)
+/// ```
+///
+/// It implements [TryFrom] which succeeds only if the passed bitfield is a valid state.
+///
+/// ```rust
+/// # use kernel::bitfield_options;
+/// #
+/// # const BIG: u32 = 16;
+/// # const SMALL: u32 = 1;
+/// #
+/// # const RED: u32 = 2;
+/// # const GREEN: u32 = 4;
+/// # const BLUE: u32 = 8;
+/// #
+/// # bitfield_options! {
+/// #     name: SizeAndColors,
+/// #     type: u32,
+/// #     options: [
+/// #         {name:big, true:BIG, false:SMALL},
+/// #         {name:red, true:RED, false:0u32},
+/// #         {name:blue, true:BLUE, false:0u32},
+/// #         {name:green, true:GREEN, false:0u32}
+/// #     ]
+/// # };
+/// # let sac = SizeAndColors::default();
+///
+/// let small_and_no_colors = SizeAndColors::try_from(SMALL)
+///    .expect("having only the SMALL bit to 1 should be a valid state");
+///
+/// // Having only the SMALL bit set to 1 is the default state.
+/// assert_eq!(sac, small_and_no_colors);
+///
+/// let big_and_rgb = SizeAndColors::try_from(BIG | RED | GREEN | BLUE)
+///     .expect("having the BIG, RED, GREEN and BLUE bits set to 1 should be a valid state");
+///
+/// assert_eq!(
+///     SizeAndColors::default()
+///         .big(true)
+///         .red(true)
+///         .blue(true)
+///         .green(true),
+///     big_and_rgb
+/// );
+///
+/// let big_and_small = SizeAndColors::try_from(BIG | SMALL)
+///     .expect_err("BIG and SMALL are incompatible, this should fail");
+///
+/// let intense:u32 = 1024;
+/// let big_and_intense = SizeAndColors::try_from(BIG | intense)
+///     .expect_err("BIG|intense is not a valid state, this should fail");
+/// ```
+#[macro_export]
+macro_rules! bitfield_options {{
+    name:$name:ident,
+    type:$t:ty,
+    options:[
+        $({name:$fn_name:ident, true: $true_bitval:expr, false: $false_bitval:expr}),+
+]} => {
+    #[doc = concat!("A wrapper around a `",
+        stringify!($t),
+        "` bitfield that sets bitflags using its ",
+        $crate::concat_with_oxford_comma!(
+        $(concat!("[",
+            stringify!($fn_name),
+            "](",
+            stringify!($name),
+            "::",
+            stringify!($fn_name),
+            ")")
+        ),+),
+        " methods.")]
+    #[derive(Debug, PartialEq)]
+    pub struct $name($t);
+
+    impl $crate::bitfield::BitField for $name {
+        type Bits = $t;
+
+        fn bits(&self) -> Self::Bits {
+            self.0
+        }
+    }
+
+    impl $name {
+    $(
+        #[doc = concat!("if `true`, unsets the `",
+            stringify!($false_bitval),
+            "` bit(s) and sets the `",
+            stringify!($true_bitval),
+            "` bit(s).  "
+        )]
+        #[doc = "Otherwise, does the opposite."]
+        pub fn $fn_name(mut self, $fn_name:bool)->Self{
+            if $fn_name{
+                self.0 = self.0 & !$false_bitval | $true_bitval;
+            }else{
+                self.0 = self.0 & !$true_bitval | $false_bitval;
+            }
+            self
+        }
+    )+
+    }
+
+    impl Default for $name{
+        fn default() -> Self {
+            Self(0)$(.$fn_name(false))+
+        }
+    }
+
+    impl TryFrom<<$name as $crate::bitfield::BitField>::Bits> for $name {
+        type Error = <$name as $crate::bitfield::BitField>::Bits;
+
+        fn try_from(value: <$name as $crate::bitfield::BitField>::Bits) -> Result<Self, Self::Error>
+        {
+            let mut to_process = value.clone();
+
+            let mut $(
+            matched = false;
+
+            for flag in [$true_bitval, $false_bitval] {
+                if (flag & to_process) == flag {
+                    matched = true;
+                    to_process -= flag;
+                    if flag > 0 {
+                        break;
+                    }
+                }
+            }
+            if !matched {
+                return Err(value);
+            })+
+
+
+            if to_process == 0 {
+                return Ok(Self(value));
+            }
+            return Err(value)
+        }
+    }
+
+    };
+}
+#[doc(inline)]
+pub use bitfield_options;
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 545d1170ee6358e185b48ce10493fc61c646155c..2428a55dca09d3c75153ccfe135a3d1dae77862e 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -31,6 +31,7 @@
 pub use ffi;
 
 pub mod alloc;
+pub mod bitfield;
 #[cfg(CONFIG_BLOCK)]
 pub mod block;
 #[doc(hidden)]

---
base-commit: ceff0757f5dafb5be5205988171809c877b1d3e3
change-id: 20241108-add-bitfield-options-macro-a847b39910aa

Best regards,
-- 
GTimothy <gtimothy-dev@...tonmail.com>



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ