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