blob: 74b2cd8afaad8d429785e86caaf531321a790e86 [file] [log] [blame]
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +00001// 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
17use crate::dice::PartialInputs;
18use crate::entry::RebootReason;
Pierre-Clément Tosi3729f652024-11-19 15:25:37 +000019use crate::fdt::read_defer_rollback_protection;
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000020use crate::instance::EntryBody;
21use crate::instance::Error as InstanceError;
22use crate::instance::{get_recorded_entry, record_instance_entry};
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000023use diced_open_dice::Hidden;
Pierre-Clément Tosi3729f652024-11-19 15:25:37 +000024use libfdt::Fdt;
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000025use log::{error, info};
26use pvmfw_avb::Capability;
27use pvmfw_avb::VerifiedBootData;
28use virtio_drivers::transport::pci::bus::PciRoot;
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +000029use vmbase::fdt::{pci::PciInfo, SwiotlbInfo};
30use vmbase::memory::init_shared_pool;
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000031use vmbase::rand;
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +000032use vmbase::virtio::pci;
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000033
34/// Performs RBP based on the input payload, current DICE chain, and host-controlled platform.
35///
36/// On success, returns a tuple containing:
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000037/// - `new_instance`: true if the legacy instance.img solution was used and a new entry created;
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000038/// - `salt`: the salt representing the instance, to be used during DICE derivation;
39/// - `defer_rollback_protection`: if RBP is being deferred.
40pub fn perform_rollback_protection(
41 fdt: &Fdt,
42 verified_boot_data: &VerifiedBootData,
43 dice_inputs: &PartialInputs,
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000044 cdi_seal: &[u8],
45 instance_hash: Option<Hidden>,
46) -> Result<(bool, Hidden, bool), RebootReason> {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000047 if should_defer_rollback_protection(fdt)?
48 && verified_boot_data.has_capability(Capability::SecretkeeperProtection)
49 {
50 perform_deferred_rollback_protection(verified_boot_data)?;
51 Ok((false, instance_hash.unwrap(), true))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000052 } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000053 perform_fixed_index_rollback_protection(verified_boot_data)?;
54 Ok((false, instance_hash.unwrap(), false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000055 } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000056 skip_rollback_protection()?;
57 Ok((false, instance_hash.unwrap(), false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000058 } else {
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +000059 perform_legacy_rollback_protection(fdt, dice_inputs, cdi_seal, instance_hash)
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000060 }
61}
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000062
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000063fn perform_deferred_rollback_protection(
64 verified_boot_data: &VerifiedBootData,
65) -> Result<(), RebootReason> {
66 info!("Deferring rollback protection");
67 // rollback_index of the image is used as security_version and is expected to be > 0 to
68 // discourage implicit allocation.
69 if verified_boot_data.rollback_index == 0 {
70 error!("Expected positive rollback_index, found 0");
71 Err(RebootReason::InvalidPayload)
72 } else {
73 Ok(())
74 }
75}
76
77fn perform_fixed_index_rollback_protection(
78 verified_boot_data: &VerifiedBootData,
79) -> Result<(), RebootReason> {
80 info!("Performing fixed-index rollback protection");
81 let fixed_index = service_vm_version::VERSION;
82 let index = verified_boot_data.rollback_index;
83 if index != fixed_index {
84 error!("Rollback index mismatch: expected {fixed_index}, found {index}");
85 Err(RebootReason::InvalidPayload)
86 } else {
87 Ok(())
88 }
89}
90
91fn skip_rollback_protection() -> Result<(), RebootReason> {
92 info!("Skipping rollback protection");
93 Ok(())
94}
95
96/// Performs RBP using instance.img where updates require clearing old entries, causing new CDIs.
97fn perform_legacy_rollback_protection(
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +000098 fdt: &Fdt,
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000099 dice_inputs: &PartialInputs,
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +0000100 cdi_seal: &[u8],
101 instance_hash: Option<Hidden>,
102) -> Result<(bool, Hidden, bool), RebootReason> {
103 info!("Fallback to instance.img based rollback checks");
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +0000104 let mut pci_root = initialize_instance_img_device(fdt)?;
105 let (recorded_entry, mut instance_img, header_index) =
106 get_recorded_entry(&mut pci_root, cdi_seal).map_err(|e| {
107 error!("Failed to get entry from instance.img: {e}");
108 RebootReason::InternalError
109 })?;
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +0000110 let (new_instance, salt) = if let Some(entry) = recorded_entry {
111 check_dice_measurements_match_entry(dice_inputs, &entry)?;
112 let salt = instance_hash.unwrap_or(entry.salt);
113 (false, salt)
114 } else {
115 // New instance!
116 let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
117 error!("Failed to generated instance.img salt: {e}");
118 RebootReason::InternalError
119 })?;
120
121 let entry = EntryBody::new(dice_inputs, &salt);
122 record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(|e| {
123 error!("Failed to get recorded entry in instance.img: {e}");
124 RebootReason::InternalError
125 })?;
126 (true, salt)
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000127 };
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +0000128 Ok((new_instance, salt, false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000129}
130
131fn check_dice_measurements_match_entry(
132 dice_inputs: &PartialInputs,
133 entry: &EntryBody,
134) -> Result<(), RebootReason> {
135 ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
136 error!(
137 "Dice measurements do not match recorded entry. \
138 This may be because of update: {e}"
139 );
140 RebootReason::InternalError
141 })?;
142
143 Ok(())
144}
145
146fn ensure_dice_measurements_match_entry(
147 dice_inputs: &PartialInputs,
148 entry: &EntryBody,
149) -> Result<(), InstanceError> {
150 if entry.code_hash != dice_inputs.code_hash {
151 Err(InstanceError::RecordedCodeHashMismatch)
152 } else if entry.auth_hash != dice_inputs.auth_hash {
153 Err(InstanceError::RecordedAuthHashMismatch)
154 } else if entry.mode() != dice_inputs.mode {
155 Err(InstanceError::RecordedDiceModeMismatch)
156 } else {
157 Ok(())
158 }
159}
160
161fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
Pierre-Clément Tosi3729f652024-11-19 15:25:37 +0000162 let defer_rbp = read_defer_rollback_protection(fdt).map_err(|e| {
163 error!("Failed to get defer-rollback-protection property in DT: {e}");
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000164 RebootReason::InvalidFdt
165 })?;
Pierre-Clément Tosi3729f652024-11-19 15:25:37 +0000166 Ok(defer_rbp.is_some())
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000167}
Pierre-Clément Tosiba666fe2024-11-19 16:44:33 +0000168
169/// Set up PCI bus and VirtIO-blk device containing the instance.img partition.
170fn initialize_instance_img_device(fdt: &Fdt) -> Result<PciRoot, RebootReason> {
171 let pci_info = PciInfo::from_fdt(fdt).map_err(|e| {
172 error!("Failed to detect PCI from DT: {e}");
173 RebootReason::InvalidFdt
174 })?;
175 let swiotlb_range = SwiotlbInfo::new_from_fdt(fdt)
176 .map_err(|e| {
177 error!("Failed to detect swiotlb from DT: {e}");
178 RebootReason::InvalidFdt
179 })?
180 .and_then(|info| info.fixed_range());
181
182 let pci_root = pci::initialize(pci_info).map_err(|e| {
183 error!("Failed to initialize PCI: {e}");
184 RebootReason::InternalError
185 })?;
186 init_shared_pool(swiotlb_range).map_err(|e| {
187 error!("Failed to initialize shared pool: {e}");
188 RebootReason::InternalError
189 })?;
190
191 Ok(pci_root)
192}