Skip to main content

product_policy/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Measured product policy: enum, per-VM value, codec.
5//!
6//! To add a product: define its body in a sibling module, then add
7//! one `N => Variant(body::Body),` line to `define_product_policy!`
8//! below. `N` is a mesh wire tag and must never be reused.
9
10#![forbid(unsafe_code)]
11
12extern crate alloc;
13
14/// Cwcow policy body and validation methods.
15pub mod cwcow;
16/// Shared helpers for product policy validation and serialization.
17pub mod product_policy_helpers;
18/// Sivm policy body and validation methods.
19pub mod sivm;
20/// UEFI enforced security settings.
21pub mod uefi_security_policy;
22
23use alloc::vec::Vec;
24
25#[doc(hidden)]
26pub use paste::paste as __paste;
27
28/// Per-VM measured product policy.
29///
30/// `None` means no policy was installed; any `Some(_)` carries the
31/// decoded variant body.
32#[derive(Debug, Clone, PartialEq, Default)]
33#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
34#[cfg_attr(feature = "inspect", inspect(transparent))]
35pub struct MeasuredProductPolicy(Option<ProductPolicy>);
36
37impl MeasuredProductPolicy {
38    /// Wrap the decoded policy (or its absence).
39    pub fn new(policy: Option<ProductPolicy>) -> Self {
40        Self(policy)
41    }
42
43    /// The decoded policy, if any.
44    pub fn raw(&self) -> Option<&ProductPolicy> {
45        self.0.as_ref()
46    }
47}
48
49#[derive(mesh_protobuf::Protobuf)]
50struct ProductPolicyInternal {
51    #[mesh(1)]
52    magic: u64,
53    #[mesh(2)]
54    policy: ProductPolicy,
55}
56
57impl ProductPolicyInternal {
58    /// Magic header for an encoded product policy payload ("OHCLPOL").
59    const MAGIC: u64 = 0x4F48434C504F4C00;
60}
61
62/// Defines the `ProductPolicy` enum and, for each variant `Foo(Body)`,
63/// a `MeasuredProductPolicy::foo(|body| ...) -> anyhow::Result<Option<T>>`
64/// accessor (`Ok(None)` when the policy is absent or a different
65/// variant; closure errors propagate).
66macro_rules! define_product_policy {
67    (
68        package = $pkg:literal ;
69        $(
70            $(#[$vmeta:meta])*
71            $tag:literal => $variant:ident ( $body:path )
72        );+ $(;)?
73    ) => {
74        /// Measured product policy. Mesh tags are part of the wire
75        /// format and must never be reused.
76        #[derive(mesh_protobuf::Protobuf, Debug, Clone, PartialEq)]
77        #[cfg_attr(feature = "manifest", derive(serde::Serialize, serde::Deserialize))]
78        #[cfg_attr(
79            feature = "manifest",
80            serde(rename_all = "snake_case", deny_unknown_fields)
81        )]
82        #[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
83        #[cfg_attr(feature = "inspect", inspect(external_tag))]
84        #[mesh(package = $pkg)]
85        pub enum ProductPolicy {
86            $(
87                $(#[$vmeta])*
88                #[mesh($tag)]
89                $variant($body),
90            )+
91        }
92
93        impl ProductPolicy {
94            /// Lowercased variant name.
95            pub fn name(&self) -> &'static str {
96                $crate::__paste! {
97                    match self {
98                        $( Self::$variant(_) => stringify!([<$variant:lower>]), )+
99                    }
100                }
101            }
102        }
103
104        $crate::__paste! {
105            $(
106                impl $crate::MeasuredProductPolicy {
107                    #[doc = concat!(
108                        "Run `f` over the `",
109                        stringify!($variant),
110                        "` body if installed. Closure errors propagate via the outer `Result`; the inner `Option` signals whether the closure ran."
111                    )]
112                    pub fn [<$variant:lower>]<T>(
113                        &self,
114                        f: impl ::core::ops::FnOnce(&$body) -> ::anyhow::Result<T>,
115                    ) -> ::anyhow::Result<::core::option::Option<T>> {
116                        match self.raw() {
117                            ::core::option::Option::Some(ProductPolicy::$variant(p)) => {
118                                f(p).map(::core::option::Option::Some)
119                            }
120                            _ => ::core::result::Result::Ok(::core::option::Option::None),
121                        }
122                    }
123                }
124            )+
125        }
126    };
127}
128
129define_product_policy! {
130    package = "openhcl.product_policy";
131
132    /// Sivm.
133    1 => Sivm(sivm::SivmPolicy);
134
135    /// Cwcow.
136    2 => Cwcow(cwcow::CwcowPolicy);
137}
138
139// --- Codec ---
140
141/// Errors from [`decode_product_policy`].
142#[derive(Debug)]
143pub enum ProductPolicyDecodeError {
144    /// `mesh_protobuf` rejected the bytes.
145    Mesh(mesh_protobuf::Error),
146    /// The decoded payload did not carry the expected magic header.
147    BadMagic,
148}
149
150impl core::fmt::Display for ProductPolicyDecodeError {
151    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152        match self {
153            Self::Mesh(_) => write!(f, "product policy mesh decode error"),
154            Self::BadMagic => write!(f, "product policy magic header mismatch"),
155        }
156    }
157}
158
159impl core::error::Error for ProductPolicyDecodeError {
160    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
161        match self {
162            Self::Mesh(e) => Some(e),
163            Self::BadMagic => None,
164        }
165    }
166}
167
168/// Encode a policy as `mesh_protobuf` bytes for the IGVM payload.
169pub fn encode_product_policy(policy: &ProductPolicy) -> Vec<u8> {
170    let policy = ProductPolicyInternal {
171        magic: ProductPolicyInternal::MAGIC,
172        policy: policy.clone(),
173    };
174    mesh_protobuf::encode(policy)
175}
176
177/// Decode `mesh_protobuf` bytes. Caller must skip empty payloads
178/// (which signal "no policy installed") before calling.
179pub fn decode_product_policy(bytes: &[u8]) -> Result<ProductPolicy, ProductPolicyDecodeError> {
180    let data: ProductPolicyInternal =
181        mesh_protobuf::decode(bytes).map_err(ProductPolicyDecodeError::Mesh)?;
182
183    if data.magic != ProductPolicyInternal::MAGIC {
184        return Err(ProductPolicyDecodeError::BadMagic);
185    }
186
187    Ok(data.policy)
188}
189
190pub use uefi_security_policy::UefiSecurityPolicy;
191
192#[cfg(test)]
193mod tests;