• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Support for guest-specific rollback protection (RBP).
16 
17 use crate::dice::PartialInputs;
18 use crate::entry::RebootReason;
19 use crate::fdt::read_defer_rollback_protection;
20 use crate::instance::EntryBody;
21 use crate::instance::Error as InstanceError;
22 use crate::instance::{get_recorded_entry, record_instance_entry};
23 use diced_open_dice::Hidden;
24 use libfdt::Fdt;
25 use log::{error, info};
26 use pvmfw_avb::Capability;
27 use pvmfw_avb::VerifiedBootData;
28 use virtio_drivers::transport::pci::bus::{ConfigurationAccess, PciRoot};
29 use vmbase::fdt::{pci::PciInfo, SwiotlbInfo};
30 use vmbase::memory::init_shared_pool;
31 use vmbase::rand;
32 use vmbase::virtio::pci;
33 
34 /// Performs RBP based on the input payload, current DICE chain, and host-controlled platform.
35 ///
36 /// On success, returns a tuple containing:
37 /// - `new_instance`: true if the legacy instance.img solution was used and a new entry created;
38 /// - `salt`: the salt representing the instance, to be used during DICE derivation;
39 /// - `defer_rollback_protection`: if RBP is being deferred.
perform_rollback_protection( fdt: &Fdt, verified_boot_data: &VerifiedBootData, dice_inputs: &PartialInputs, cdi_seal: &[u8], ) -> Result<(bool, Hidden, bool), RebootReason>40 pub fn perform_rollback_protection(
41     fdt: &Fdt,
42     verified_boot_data: &VerifiedBootData,
43     dice_inputs: &PartialInputs,
44     cdi_seal: &[u8],
45 ) -> Result<(bool, Hidden, bool), RebootReason> {
46     let instance_hash = dice_inputs.instance_hash;
47     if let Some(fixed) = get_fixed_rollback_protection(verified_boot_data) {
48         // Prevent attackers from impersonating well-known images.
49         perform_fixed_index_rollback_protection(verified_boot_data, fixed)?;
50         Ok((false, instance_hash.unwrap(), false))
51     } else if (should_defer_rollback_protection(fdt)?
52         && verified_boot_data.has_capability(Capability::SecretkeeperProtection))
53         || verified_boot_data.has_capability(Capability::TrustySecurityVm)
54     {
55         perform_deferred_rollback_protection(verified_boot_data)?;
56         Ok((false, instance_hash.unwrap(), true))
57     } else {
58         perform_legacy_rollback_protection(fdt, dice_inputs, cdi_seal, instance_hash)
59     }
60 }
61 
perform_deferred_rollback_protection( verified_boot_data: &VerifiedBootData, ) -> Result<(), RebootReason>62 fn perform_deferred_rollback_protection(
63     verified_boot_data: &VerifiedBootData,
64 ) -> Result<(), RebootReason> {
65     info!("Deferring rollback protection");
66     // rollback_index of the image is used as security_version and is expected to be > 0 to
67     // discourage implicit allocation.
68     if verified_boot_data.rollback_index == 0 {
69         error!("Expected positive rollback_index, found 0");
70         Err(RebootReason::InvalidPayload)
71     } else {
72         Ok(())
73     }
74 }
75 
get_fixed_rollback_protection(verified_boot_data: &VerifiedBootData) -> Option<u64>76 fn get_fixed_rollback_protection(verified_boot_data: &VerifiedBootData) -> Option<u64> {
77     if verified_boot_data.has_capability(Capability::RemoteAttest) {
78         Some(service_vm_version::VERSION)
79     } else {
80         None
81     }
82 }
83 
perform_fixed_index_rollback_protection( verified_boot_data: &VerifiedBootData, fixed_index: u64, ) -> Result<(), RebootReason>84 fn perform_fixed_index_rollback_protection(
85     verified_boot_data: &VerifiedBootData,
86     fixed_index: u64,
87 ) -> Result<(), RebootReason> {
88     info!("Performing fixed-index rollback protection");
89     let index = verified_boot_data.rollback_index;
90     if index != fixed_index {
91         error!("Rollback index mismatch: expected {fixed_index}, found {index}");
92         Err(RebootReason::InvalidPayload)
93     } else {
94         Ok(())
95     }
96 }
97 
98 /// Performs RBP using instance.img where updates require clearing old entries, causing new CDIs.
perform_legacy_rollback_protection( fdt: &Fdt, dice_inputs: &PartialInputs, cdi_seal: &[u8], instance_hash: Option<Hidden>, ) -> Result<(bool, Hidden, bool), RebootReason>99 fn perform_legacy_rollback_protection(
100     fdt: &Fdt,
101     dice_inputs: &PartialInputs,
102     cdi_seal: &[u8],
103     instance_hash: Option<Hidden>,
104 ) -> Result<(bool, Hidden, bool), RebootReason> {
105     info!("Fallback to instance.img based rollback checks");
106     let mut pci_root = initialize_instance_img_device(fdt)?;
107     let (recorded_entry, mut instance_img, header_index) =
108         get_recorded_entry(&mut pci_root, cdi_seal).map_err(|e| {
109             error!("Failed to get entry from instance.img: {e}");
110             RebootReason::InternalError
111         })?;
112     let (new_instance, salt) = if let Some(entry) = recorded_entry {
113         check_dice_measurements_match_entry(dice_inputs, &entry)?;
114         let salt = instance_hash.unwrap_or(entry.salt);
115         (false, salt)
116     } else {
117         // New instance!
118         let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
119             error!("Failed to generated instance.img salt: {e}");
120             RebootReason::InternalError
121         })?;
122 
123         let entry = EntryBody::new(dice_inputs, &salt);
124         record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(|e| {
125             error!("Failed to get recorded entry in instance.img: {e}");
126             RebootReason::InternalError
127         })?;
128         (true, salt)
129     };
130     Ok((new_instance, salt, false))
131 }
132 
check_dice_measurements_match_entry( dice_inputs: &PartialInputs, entry: &EntryBody, ) -> Result<(), RebootReason>133 fn check_dice_measurements_match_entry(
134     dice_inputs: &PartialInputs,
135     entry: &EntryBody,
136 ) -> Result<(), RebootReason> {
137     ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
138         error!(
139             "Dice measurements do not match recorded entry. \
140         This may be because of update: {e}"
141         );
142         RebootReason::InternalError
143     })?;
144 
145     Ok(())
146 }
147 
ensure_dice_measurements_match_entry( dice_inputs: &PartialInputs, entry: &EntryBody, ) -> Result<(), InstanceError>148 fn ensure_dice_measurements_match_entry(
149     dice_inputs: &PartialInputs,
150     entry: &EntryBody,
151 ) -> Result<(), InstanceError> {
152     if entry.code_hash != dice_inputs.code_hash {
153         Err(InstanceError::RecordedCodeHashMismatch)
154     } else if entry.auth_hash != dice_inputs.auth_hash {
155         Err(InstanceError::RecordedAuthHashMismatch)
156     } else if entry.mode() != dice_inputs.mode {
157         Err(InstanceError::RecordedDiceModeMismatch)
158     } else {
159         Ok(())
160     }
161 }
162 
should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason>163 fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
164     let defer_rbp = read_defer_rollback_protection(fdt).map_err(|e| {
165         error!("Failed to get defer-rollback-protection property in DT: {e}");
166         RebootReason::InvalidFdt
167     })?;
168     Ok(defer_rbp.is_some())
169 }
170 
171 /// Set up PCI bus and VirtIO-blk device containing the instance.img partition.
initialize_instance_img_device( fdt: &Fdt, ) -> Result<PciRoot<impl ConfigurationAccess>, RebootReason>172 fn initialize_instance_img_device(
173     fdt: &Fdt,
174 ) -> Result<PciRoot<impl ConfigurationAccess>, RebootReason> {
175     let pci_info = PciInfo::from_fdt(fdt).map_err(|e| {
176         error!("Failed to detect PCI from DT: {e}");
177         RebootReason::InvalidFdt
178     })?;
179     let swiotlb_range = SwiotlbInfo::new_from_fdt(fdt)
180         .map_err(|e| {
181             error!("Failed to detect swiotlb from DT: {e}");
182             RebootReason::InvalidFdt
183         })?
184         .and_then(|info| info.fixed_range());
185 
186     let pci_root = pci::initialize(pci_info).map_err(|e| {
187         error!("Failed to initialize PCI: {e}");
188         RebootReason::InternalError
189     })?;
190     init_shared_pool(swiotlb_range).map_err(|e| {
191         error!("Failed to initialize shared pool: {e}");
192         RebootReason::InternalError
193     })?;
194 
195     Ok(pci_root)
196 }
197