Skip to main content

fbl/
canary.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7/// An embeddable structure guard.
8///
9/// To use `fbl::Canary`, choose a 4-byte guard value.
10///
11/// You can use the `Canary::new()` method to instantiate a `Canary` object.
12/// The compiler will infer the magic value from the type definition.
13///
14/// ```rust
15/// struct MyStruct {
16///     canary: fbl::Canary<{ fbl::magic(b"guar") }>,
17///     // ...
18/// }
19///
20/// impl MyStruct {
21///     fn new() -> Self {
22///         MyStruct {
23///             canary: fbl::Canary::new(),
24///             // ...
25///         }
26///     }
27/// }
28/// ```
29///
30/// The canary initializes itself with the guard value during construction and
31/// checks it during destruction (on `Drop`). You can also manually check the
32/// value during the lifetime of your object by calling the `assert` method.
33///
34/// If the value is not an ASCII string, you can directly use an integer literal
35/// as the const generic parameter.
36///
37/// ```rust
38/// struct MyStruct {
39///     canary: fbl::Canary<0x12345678>,
40///     // ...
41/// }
42/// ```
43#[repr(C)]
44pub struct Canary<const MAGIC: u32> {
45    magic: u32,
46}
47
48zr::static_assert!(core::mem::size_of::<Canary<{ magic(b"test") }>>() == 4);
49zr::static_assert!(core::mem::align_of::<Canary<{ magic(b"test") }>>() == 4);
50
51impl<const MAGIC: u32> Canary<MAGIC> {
52    /// Create a new Canary with the specified magic value.
53    pub const fn new() -> Self {
54        Canary { magic: MAGIC }
55    }
56
57    /// Assert that the value of `magic` is as expected.
58    ///
59    /// # Panics
60    ///
61    /// Panics if `self.magic` is not the expected value.
62    pub fn assert(&self) {
63        let observed_magic = unsafe { core::ptr::read_volatile(&self.magic) };
64        if observed_magic != MAGIC {
65            panic!("Invalid canary (expt: {:08x}, got: {:08x})", MAGIC, observed_magic);
66        }
67    }
68
69    /// Some places have special handling of bad magic values. For these
70    /// cases, simply return whether the `magic` is correct, and let
71    /// them respond appropriately if not.
72    pub fn valid(&self) -> bool {
73        let observed_magic = unsafe { core::ptr::read_volatile(&self.magic) };
74        observed_magic == MAGIC
75    }
76}
77
78impl<const MAGIC: u32> Default for Canary<MAGIC> {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl<const MAGIC: u32> Drop for Canary<MAGIC> {
85    fn drop(&mut self) {
86        self.assert();
87        unsafe {
88            core::ptr::write_volatile(&mut self.magic, 0);
89        }
90    }
91}
92
93/// Function for generating canary magic values from strings
94pub const fn magic(str: &[u8; 4]) -> u32 {
95    ((str[0] as u32) << 24) | ((str[1] as u32) << 16) | ((str[2] as u32) << 8) | (str[3] as u32)
96}
97
98#[macro_export]
99macro_rules! canary {
100    ($str:literal) => {
101        $crate::Canary::<{ $crate::magic($str) }>::new()
102    };
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_canary_default() {
111        let canary: Canary<{ magic(b"test") }> = Default::default();
112        assert!(canary.valid());
113    }
114
115    #[test]
116    fn test_magic_runtime() {
117        let m = magic(b"abcd");
118        assert_eq!(m, 0x61626364);
119    }
120
121    #[test]
122    fn test_canary() {
123        let canary = canary!(b"test");
124        assert!(canary.valid());
125        canary.assert();
126    }
127
128    #[test]
129    #[should_panic(expected = "Invalid canary")]
130    fn test_canary_corruption() {
131        let mut canary = canary!(b"test");
132        // Corrupt the canary storage directly
133        unsafe {
134            core::ptr::write_volatile(&mut canary.magic, 0);
135        }
136        canary.assert();
137    }
138
139    unsafe extern "C" {
140        fn check_rust_canary(ptr: *const core::ffi::c_void, expected_magic: u32) -> bool;
141    }
142
143    #[test]
144    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
145    fn test_canary_ffi() {
146        const MAGIC_VAL: u32 = 0x12345678; // Must match the hardcoded value in C++ helper
147        let canary = Canary::<MAGIC_VAL>::new();
148        unsafe {
149            assert!(check_rust_canary(&canary as *const _ as *const core::ffi::c_void, MAGIC_VAL));
150        }
151    }
152}