Skip to main content

rvf_types/
kernel_binding.rs

1//! Kernel binding types for the RVF computational container.
2//!
3//! Defines the 128-byte `KernelBinding` struct per ADR-031 (revised).
4//! A `KernelBinding` cryptographically ties a manifest root to a
5//! policy hash with a version stamp, ensuring tamper-evident linkage.
6//!
7//! Padded to 128 bytes to avoid future wire-format breaks. Active fields
8//! occupy 76 bytes; the remaining 52 bytes are reserved/padding (must be zero).
9
10/// 128-byte kernel binding record (padded for future evolution).
11///
12/// Layout:
13/// | Offset | Size | Field                |
14/// |--------|------|----------------------|
15/// | 0x00   | 32   | manifest_root_hash   |
16/// | 0x20   | 32   | policy_hash          |
17/// | 0x40   | 2    | binding_version      |
18/// | 0x42   | 2    | min_runtime_version  |
19/// | 0x44   | 4    | _pad0 (alignment)    |
20/// | 0x48   | 8    | allowed_segment_mask |
21/// | 0x50   | 48   | _reserved            |
22///
23/// All multi-byte fields are little-endian on the wire.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[repr(C)]
26pub struct KernelBinding {
27    /// SHAKE-256-256 of the manifest root node.
28    pub manifest_root_hash: [u8; 32],
29    /// SHAKE-256-256 of the policy document.
30    pub policy_hash: [u8; 32],
31    /// Binding format version (currently 1).
32    pub binding_version: u16,
33    /// Minimum runtime version required (0 = any).
34    pub min_runtime_version: u16,
35    /// Alignment padding (must be zero).
36    pub _pad0: u32,
37    /// Bitmask of allowed segment types (0 = no restriction).
38    pub allowed_segment_mask: u64,
39    /// Reserved for future use (must be zero).
40    pub _reserved: [u8; 48],
41}
42
43// Compile-time assertion: KernelBinding must be exactly 128 bytes.
44const _: () = assert!(core::mem::size_of::<KernelBinding>() == 128);
45
46impl KernelBinding {
47    /// Serialize the binding to a 128-byte array.
48    pub fn to_bytes(&self) -> [u8; 128] {
49        let mut buf = [0u8; 128];
50        buf[0x00..0x20].copy_from_slice(&self.manifest_root_hash);
51        buf[0x20..0x40].copy_from_slice(&self.policy_hash);
52        buf[0x40..0x42].copy_from_slice(&self.binding_version.to_le_bytes());
53        buf[0x42..0x44].copy_from_slice(&self.min_runtime_version.to_le_bytes());
54        buf[0x44..0x48].copy_from_slice(&self._pad0.to_le_bytes());
55        buf[0x48..0x50].copy_from_slice(&self.allowed_segment_mask.to_le_bytes());
56        buf[0x50..0x80].copy_from_slice(&self._reserved);
57        buf
58    }
59
60    /// Deserialize a `KernelBinding` from a 128-byte slice (unchecked).
61    ///
62    /// Does NOT validate reserved fields. Use `from_bytes_validated` for
63    /// security-critical paths that must reject non-zero padding/reserved.
64    pub fn from_bytes(data: &[u8; 128]) -> Self {
65        Self {
66            manifest_root_hash: {
67                let mut h = [0u8; 32];
68                h.copy_from_slice(&data[0x00..0x20]);
69                h
70            },
71            policy_hash: {
72                let mut h = [0u8; 32];
73                h.copy_from_slice(&data[0x20..0x40]);
74                h
75            },
76            binding_version: u16::from_le_bytes([data[0x40], data[0x41]]),
77            min_runtime_version: u16::from_le_bytes([data[0x42], data[0x43]]),
78            _pad0: u32::from_le_bytes([data[0x44], data[0x45], data[0x46], data[0x47]]),
79            allowed_segment_mask: u64::from_le_bytes([
80                data[0x48], data[0x49], data[0x4A], data[0x4B],
81                data[0x4C], data[0x4D], data[0x4E], data[0x4F],
82            ]),
83            _reserved: {
84                let mut r = [0u8; 48];
85                r.copy_from_slice(&data[0x50..0x80]);
86                r
87            },
88        }
89    }
90
91    /// Deserialize and validate a `KernelBinding` from a 128-byte slice.
92    ///
93    /// Rejects bindings where:
94    /// - `binding_version` is 0 (uninitialized)
95    /// - `_pad0` is non-zero (spec violation)
96    /// - `_reserved` contains non-zero bytes (spec violation / data smuggling)
97    pub fn from_bytes_validated(data: &[u8; 128]) -> Result<Self, &'static str> {
98        let binding = Self::from_bytes(data);
99        if binding.binding_version == 0 {
100            return Err("binding_version must be > 0");
101        }
102        if binding._pad0 != 0 {
103            return Err("_pad0 must be zero");
104        }
105        if binding._reserved.iter().any(|&b| b != 0) {
106            return Err("_reserved must be all zeros");
107        }
108        Ok(binding)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    fn sample_binding() -> KernelBinding {
117        KernelBinding {
118            manifest_root_hash: [0xAA; 32],
119            policy_hash: [0xBB; 32],
120            binding_version: 1,
121            min_runtime_version: 0,
122            _pad0: 0,
123            allowed_segment_mask: 0,
124            _reserved: [0; 48],
125        }
126    }
127
128    #[test]
129    fn binding_size_is_128() {
130        assert_eq!(core::mem::size_of::<KernelBinding>(), 128);
131    }
132
133    #[test]
134    fn round_trip_serialization() {
135        let original = sample_binding();
136        let bytes = original.to_bytes();
137        let decoded = KernelBinding::from_bytes(&bytes);
138
139        assert_eq!(decoded.manifest_root_hash, [0xAA; 32]);
140        assert_eq!(decoded.policy_hash, [0xBB; 32]);
141        assert_eq!(decoded.binding_version, 1);
142        assert_eq!(decoded.min_runtime_version, 0);
143        assert_eq!(decoded._pad0, 0);
144        assert_eq!(decoded.allowed_segment_mask, 0);
145        assert_eq!(decoded._reserved, [0; 48]);
146    }
147
148    #[test]
149    fn round_trip_with_fields() {
150        let binding = KernelBinding {
151            manifest_root_hash: [0x11; 32],
152            policy_hash: [0x22; 32],
153            binding_version: 2,
154            min_runtime_version: 3,
155            _pad0: 0,
156            allowed_segment_mask: 0x00FF_FFFF,
157            _reserved: [0; 48],
158        };
159        let bytes = binding.to_bytes();
160        let decoded = KernelBinding::from_bytes(&bytes);
161        assert_eq!(decoded.binding_version, 2);
162        assert_eq!(decoded.min_runtime_version, 3);
163        assert_eq!(decoded.allowed_segment_mask, 0x00FF_FFFF);
164    }
165
166    #[test]
167    fn field_offsets() {
168        let b = sample_binding();
169        let base = &b as *const _ as usize;
170
171        assert_eq!(&b.manifest_root_hash as *const _ as usize - base, 0x00);
172        assert_eq!(&b.policy_hash as *const _ as usize - base, 0x20);
173        assert_eq!(&b.binding_version as *const _ as usize - base, 0x40);
174        assert_eq!(&b.min_runtime_version as *const _ as usize - base, 0x42);
175        assert_eq!(&b._pad0 as *const _ as usize - base, 0x44);
176        assert_eq!(&b.allowed_segment_mask as *const _ as usize - base, 0x48);
177        assert_eq!(&b._reserved as *const _ as usize - base, 0x50);
178    }
179
180    #[test]
181    fn reserved_must_be_zero_in_new_bindings() {
182        let b = sample_binding();
183        assert!(b._reserved.iter().all(|&x| x == 0));
184        assert_eq!(b._pad0, 0);
185    }
186}