blob: e79705fe9e16b9f7581967c4130a5b654b6e6ddb [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;
19use crate::instance::EntryBody;
20use crate::instance::Error as InstanceError;
21use crate::instance::{get_recorded_entry, record_instance_entry};
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000022use diced_open_dice::Hidden;
23use libfdt::{Fdt, FdtNode};
24use log::{error, info};
25use pvmfw_avb::Capability;
26use pvmfw_avb::VerifiedBootData;
27use virtio_drivers::transport::pci::bus::PciRoot;
28use vmbase::rand;
29
30/// Performs RBP based on the input payload, current DICE chain, and host-controlled platform.
31///
32/// On success, returns a tuple containing:
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000033/// - `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 +000034/// - `salt`: the salt representing the instance, to be used during DICE derivation;
35/// - `defer_rollback_protection`: if RBP is being deferred.
36pub fn perform_rollback_protection(
37 fdt: &Fdt,
38 verified_boot_data: &VerifiedBootData,
39 dice_inputs: &PartialInputs,
40 pci_root: &mut PciRoot,
41 cdi_seal: &[u8],
42 instance_hash: Option<Hidden>,
43) -> Result<(bool, Hidden, bool), RebootReason> {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000044 if should_defer_rollback_protection(fdt)?
45 && verified_boot_data.has_capability(Capability::SecretkeeperProtection)
46 {
47 perform_deferred_rollback_protection(verified_boot_data)?;
48 Ok((false, instance_hash.unwrap(), true))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000049 } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000050 perform_fixed_index_rollback_protection(verified_boot_data)?;
51 Ok((false, instance_hash.unwrap(), false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000052 } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000053 skip_rollback_protection()?;
54 Ok((false, instance_hash.unwrap(), false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000055 } else {
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000056 perform_legacy_rollback_protection(dice_inputs, pci_root, cdi_seal, instance_hash)
57 }
58}
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +000059
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +000060fn perform_deferred_rollback_protection(
61 verified_boot_data: &VerifiedBootData,
62) -> Result<(), RebootReason> {
63 info!("Deferring rollback protection");
64 // rollback_index of the image is used as security_version and is expected to be > 0 to
65 // discourage implicit allocation.
66 if verified_boot_data.rollback_index == 0 {
67 error!("Expected positive rollback_index, found 0");
68 Err(RebootReason::InvalidPayload)
69 } else {
70 Ok(())
71 }
72}
73
74fn perform_fixed_index_rollback_protection(
75 verified_boot_data: &VerifiedBootData,
76) -> Result<(), RebootReason> {
77 info!("Performing fixed-index rollback protection");
78 let fixed_index = service_vm_version::VERSION;
79 let index = verified_boot_data.rollback_index;
80 if index != fixed_index {
81 error!("Rollback index mismatch: expected {fixed_index}, found {index}");
82 Err(RebootReason::InvalidPayload)
83 } else {
84 Ok(())
85 }
86}
87
88fn skip_rollback_protection() -> Result<(), RebootReason> {
89 info!("Skipping rollback protection");
90 Ok(())
91}
92
93/// Performs RBP using instance.img where updates require clearing old entries, causing new CDIs.
94fn perform_legacy_rollback_protection(
95 dice_inputs: &PartialInputs,
96 pci_root: &mut PciRoot,
97 cdi_seal: &[u8],
98 instance_hash: Option<Hidden>,
99) -> Result<(bool, Hidden, bool), RebootReason> {
100 info!("Fallback to instance.img based rollback checks");
101 let (recorded_entry, mut instance_img, header_index) = get_recorded_entry(pci_root, cdi_seal)
102 .map_err(|e| {
103 error!("Failed to get entry from instance.img: {e}");
104 RebootReason::InternalError
105 })?;
106 let (new_instance, salt) = if let Some(entry) = recorded_entry {
107 check_dice_measurements_match_entry(dice_inputs, &entry)?;
108 let salt = instance_hash.unwrap_or(entry.salt);
109 (false, salt)
110 } else {
111 // New instance!
112 let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
113 error!("Failed to generated instance.img salt: {e}");
114 RebootReason::InternalError
115 })?;
116
117 let entry = EntryBody::new(dice_inputs, &salt);
118 record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(|e| {
119 error!("Failed to get recorded entry in instance.img: {e}");
120 RebootReason::InternalError
121 })?;
122 (true, salt)
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000123 };
Pierre-Clément Tosi495c67a2024-11-27 16:32:04 +0000124 Ok((new_instance, salt, false))
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000125}
126
127fn check_dice_measurements_match_entry(
128 dice_inputs: &PartialInputs,
129 entry: &EntryBody,
130) -> Result<(), RebootReason> {
131 ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
132 error!(
133 "Dice measurements do not match recorded entry. \
134 This may be because of update: {e}"
135 );
136 RebootReason::InternalError
137 })?;
138
139 Ok(())
140}
141
142fn ensure_dice_measurements_match_entry(
143 dice_inputs: &PartialInputs,
144 entry: &EntryBody,
145) -> Result<(), InstanceError> {
146 if entry.code_hash != dice_inputs.code_hash {
147 Err(InstanceError::RecordedCodeHashMismatch)
148 } else if entry.auth_hash != dice_inputs.auth_hash {
149 Err(InstanceError::RecordedAuthHashMismatch)
150 } else if entry.mode() != dice_inputs.mode {
151 Err(InstanceError::RecordedDiceModeMismatch)
152 } else {
153 Ok(())
154 }
155}
156
157fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
Pierre-Clément Tosif89759c2024-11-19 15:25:37 +0000158 let Some(node) = avf_untrusted_node(fdt)? else { return Ok(false) };
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000159 let defer_rbp = node
Alan Stokesf46a17c2025-01-05 15:50:18 +0000160 .getprop(c"defer-rollback-protection")
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000161 .map_err(|e| {
162 error!("Failed to get defer-rollback-protection property in DT: {e}");
163 RebootReason::InvalidFdt
164 })?
165 .is_some();
166 Ok(defer_rbp)
167}
168
Pierre-Clément Tosif89759c2024-11-19 15:25:37 +0000169fn avf_untrusted_node(fdt: &Fdt) -> Result<Option<FdtNode>, RebootReason> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000170 let node = fdt.node(c"/avf/untrusted").map_err(|e| {
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000171 error!("Failed to get /avf/untrusted node: {e}");
172 RebootReason::InvalidFdt
173 })?;
Pierre-Clément Tosif89759c2024-11-19 15:25:37 +0000174 Ok(node)
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000175}