blob: 15d22b3df5947601a527df50a81c0074b85d6255 [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:
33/// - `new_instance`: true if a new entry was created using the legacy instance.img solution;
34/// - `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> {
44 let defer_rollback_protection = should_defer_rollback_protection(fdt)?
45 && verified_boot_data.has_capability(Capability::SecretkeeperProtection);
46 let (new_instance, salt) = if defer_rollback_protection {
47 info!("Guest OS is capable of Secretkeeper protection, deferring rollback protection");
48 // rollback_index of the image is used as security_version and is expected to be > 0 to
49 // discourage implicit allocation.
50 if verified_boot_data.rollback_index == 0 {
51 error!("Expected positive rollback_index, found 0");
52 return Err(RebootReason::InvalidPayload);
53 };
54 (false, instance_hash.unwrap())
55 } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
56 info!("Service VM capable of remote attestation detected, performing version checks");
57 if service_vm_version::VERSION != verified_boot_data.rollback_index {
58 // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
59 // the one embedded in pvmfw at build time.
60 // This prevents the pvmfw from booting a roll backed RKP VM.
61 error!(
62 "Service VM version mismatch: expected {}, found {}",
63 service_vm_version::VERSION,
64 verified_boot_data.rollback_index
65 );
66 return Err(RebootReason::InvalidPayload);
67 }
68 (false, instance_hash.unwrap())
69 } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
70 // The rollback protection of Trusty VMs are handled by AuthMgr, so we don't need to
71 // handle it here.
72 info!("Trusty Security VM detected");
73 (false, instance_hash.unwrap())
74 } else {
75 info!("Fallback to instance.img based rollback checks");
76 let (recorded_entry, mut instance_img, header_index) =
77 get_recorded_entry(pci_root, cdi_seal).map_err(|e| {
78 error!("Failed to get entry from instance.img: {e}");
79 RebootReason::InternalError
80 })?;
81 let (new_instance, salt) = if let Some(entry) = recorded_entry {
82 check_dice_measurements_match_entry(dice_inputs, &entry)?;
83 let salt = instance_hash.unwrap_or(entry.salt);
84 (false, salt)
85 } else {
86 // New instance!
87 let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
88 error!("Failed to generated instance.img salt: {e}");
89 RebootReason::InternalError
90 })?;
91
92 let entry = EntryBody::new(dice_inputs, &salt);
93 record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(
94 |e| {
95 error!("Failed to get recorded entry in instance.img: {e}");
96 RebootReason::InternalError
97 },
98 )?;
99 (true, salt)
100 };
101 (new_instance, salt)
102 };
103
104 Ok((new_instance, salt, defer_rollback_protection))
105}
106
107fn check_dice_measurements_match_entry(
108 dice_inputs: &PartialInputs,
109 entry: &EntryBody,
110) -> Result<(), RebootReason> {
111 ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
112 error!(
113 "Dice measurements do not match recorded entry. \
114 This may be because of update: {e}"
115 );
116 RebootReason::InternalError
117 })?;
118
119 Ok(())
120}
121
122fn ensure_dice_measurements_match_entry(
123 dice_inputs: &PartialInputs,
124 entry: &EntryBody,
125) -> Result<(), InstanceError> {
126 if entry.code_hash != dice_inputs.code_hash {
127 Err(InstanceError::RecordedCodeHashMismatch)
128 } else if entry.auth_hash != dice_inputs.auth_hash {
129 Err(InstanceError::RecordedAuthHashMismatch)
130 } else if entry.mode() != dice_inputs.mode {
131 Err(InstanceError::RecordedDiceModeMismatch)
132 } else {
133 Ok(())
134 }
135}
136
137fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
138 let node = avf_untrusted_node(fdt)?;
139 let defer_rbp = node
Alan Stokesf46a17c2025-01-05 15:50:18 +0000140 .getprop(c"defer-rollback-protection")
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000141 .map_err(|e| {
142 error!("Failed to get defer-rollback-protection property in DT: {e}");
143 RebootReason::InvalidFdt
144 })?
145 .is_some();
146 Ok(defer_rbp)
147}
148
149fn avf_untrusted_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000150 let node = fdt.node(c"/avf/untrusted").map_err(|e| {
Pierre-Clément Tosi10bea6f2024-11-26 22:04:10 +0000151 error!("Failed to get /avf/untrusted node: {e}");
152 RebootReason::InvalidFdt
153 })?;
154 node.ok_or_else(|| {
155 error!("/avf/untrusted node is missing in DT");
156 RebootReason::InvalidFdt
157 })
158}