blob: a998bfbb2b8d43005bf555c1ee8e68694d1a3552 [file] [log] [blame]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +00001// Copyright 2023, 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 reading and writing to the instance.img.
16
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000017use crate::crypto;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000018use crate::crypto::AeadCtx;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000019use crate::dice::PartialInputs;
20use crate::gpt;
21use crate::gpt::Partition;
22use crate::gpt::Partitions;
Alice Wang947f3f72023-09-29 09:04:07 +000023use bssl_avf::{self, hkdf, Digester};
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000024use core::fmt;
25use core::mem::size_of;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000026use diced_open_dice::DiceMode;
27use diced_open_dice::Hash;
28use diced_open_dice::Hidden;
29use log::trace;
30use uuid::Uuid;
Alice Wang0e086232023-06-12 13:47:40 +000031use virtio_drivers::transport::{pci::bus::PciRoot, DeviceType, Transport};
Pierre-Clément Tosi3e3d7332023-06-22 10:44:29 +000032use vmbase::rand;
Alice Wangeacb7382023-06-05 12:53:54 +000033use vmbase::util::ceiling_div;
Alice Wang0e086232023-06-12 13:47:40 +000034use vmbase::virtio::pci::{PciTransportIterator, VirtIOBlk};
Alice Wang7c55c7d2023-07-05 14:51:40 +000035use vmbase::virtio::HalImpl;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +010036use zerocopy::AsBytes;
37use zerocopy::FromBytes;
Frederick Mayle2e779942023-10-15 18:27:31 +000038use zerocopy::FromZeroes;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000039
40pub enum Error {
41 /// Unexpected I/O error while accessing the underlying disk.
42 FailedIo(gpt::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000043 /// Failed to decrypt the entry.
44 FailedOpen(crypto::ErrorIterator),
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000045 /// Failed to generate a random salt to be stored.
46 FailedSaltGeneration(rand::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000047 /// Failed to encrypt the entry.
48 FailedSeal(crypto::ErrorIterator),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000049 /// Impossible to create a new instance.img entry.
50 InstanceImageFull,
51 /// Badly formatted instance.img header block.
52 InvalidInstanceImageHeader,
53 /// No instance.img ("vm-instance") partition found.
54 MissingInstanceImage,
55 /// The instance.img doesn't contain a header.
56 MissingInstanceImageHeader,
57 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
58 RecordedAuthHashMismatch,
59 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
60 RecordedCodeHashMismatch,
61 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
62 RecordedDiceModeMismatch,
63 /// Size of the instance.img entry being read or written is not supported.
64 UnsupportedEntrySize(usize),
Alice Wang0e086232023-06-12 13:47:40 +000065 /// Failed to create VirtIO Block device.
66 VirtIOBlkCreationFailed(virtio_drivers::Error),
Alice Wang947f3f72023-09-29 09:04:07 +000067 /// An error happened during the interaction with BoringSSL.
68 BoringSslFailed(bssl_avf::Error),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000069}
70
71impl fmt::Display for Error {
72 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73 match self {
74 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000075 Self::FailedOpen(e_iter) => {
76 writeln!(f, "Failed to open the instance.img partition:")?;
77 for e in *e_iter {
78 writeln!(f, "\t{e}")?;
79 }
80 Ok(())
81 }
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000082 Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000083 Self::FailedSeal(e_iter) => {
84 writeln!(f, "Failed to seal the instance.img partition:")?;
85 for e in *e_iter {
86 writeln!(f, "\t{e}")?;
87 }
88 Ok(())
89 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000090 Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
91 Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
92 Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
93 Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
94 Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
95 Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
96 Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
97 Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
Alice Wang0e086232023-06-12 13:47:40 +000098 Self::VirtIOBlkCreationFailed(e) => {
99 write!(f, "Failed to create VirtIO Block device: {e}")
100 }
Alice Wang947f3f72023-09-29 09:04:07 +0000101 Self::BoringSslFailed(e) => {
102 write!(f, "An error happened during the interaction with BoringSSL: {e}")
103 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000104 }
105 }
106}
107
Alice Wang947f3f72023-09-29 09:04:07 +0000108impl From<bssl_avf::Error> for Error {
109 fn from(e: bssl_avf::Error) -> Self {
110 Self::BoringSslFailed(e)
111 }
112}
113
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000114pub type Result<T> = core::result::Result<T, Error>;
115
116pub fn get_or_generate_instance_salt(
117 pci_root: &mut PciRoot,
118 dice_inputs: &PartialInputs,
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000119 secret: &[u8],
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000120) -> Result<(bool, Hidden)> {
121 let mut instance_img = find_instance_img(pci_root)?;
122
123 let entry = locate_entry(&mut instance_img)?;
124 trace!("Found pvmfw instance.img entry: {entry:?}");
125
Alice Wang947f3f72023-09-29 09:04:07 +0000126 let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000127 let mut blk = [0; BLK_SIZE];
128 match entry {
129 PvmfwEntry::Existing { header_index, payload_size } => {
130 if payload_size > blk.len() {
131 // We currently only support single-blk entries.
132 return Err(Error::UnsupportedEntrySize(payload_size));
133 }
134 let payload_index = header_index + 1;
135 instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
136
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000137 let payload = &blk[..payload_size];
138 let mut entry = [0; size_of::<EntryBody>()];
Alice Wangccc52e52023-10-03 13:14:09 +0000139 let aead =
140 AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedOpen)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000141 let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000142
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100143 let body = EntryBody::read_from(decrypted).unwrap();
Alice Wang12e4c862023-12-11 13:52:27 +0000144 if dice_inputs.rkp_vm_marker {
145 // The RKP VM is allowed to run if it has passed the verified boot check and
146 // contains the expected version in its AVB footer.
147 // The comparison below with the previous boot information is skipped to enable the
148 // simultaneous update of the pvmfw and RKP VM.
149 // For instance, when both the pvmfw and RKP VM are updated, the code hash of the
150 // RKP VM will differ from the one stored in the instance image. In this case, the
151 // RKP VM is still allowed to run.
152 // This ensures that the updated RKP VM will retain the same CDIs in the next stage.
153 return Ok((false, body.salt));
154 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000155 if body.code_hash != dice_inputs.code_hash {
156 Err(Error::RecordedCodeHashMismatch)
157 } else if body.auth_hash != dice_inputs.auth_hash {
158 Err(Error::RecordedAuthHashMismatch)
159 } else if body.mode() != dice_inputs.mode {
160 Err(Error::RecordedDiceModeMismatch)
161 } else {
162 Ok((false, body.salt))
163 }
164 }
165 PvmfwEntry::New { header_index } => {
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +0000166 let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100167 let body = EntryBody::new(dice_inputs, &salt);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000168
Alice Wangccc52e52023-10-03 13:14:09 +0000169 let aead =
170 AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000171 // We currently only support single-blk entries.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100172 let plaintext = body.as_bytes();
173 assert!(plaintext.len() + aead.aead().unwrap().max_overhead() < blk.len());
174 let encrypted = aead.seal(&mut blk, plaintext).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000175 let payload_size = encrypted.len();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000176 let payload_index = header_index + 1;
177 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
178
179 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100180 header.write_to_prefix(blk.as_mut_slice()).unwrap();
181 blk[header.as_bytes().len()..].fill(0);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000182 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
183
184 Ok((true, salt))
185 }
186 }
187}
188
Frederick Mayle2e779942023-10-15 18:27:31 +0000189#[derive(FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000190#[repr(C, packed)]
191struct Header {
192 magic: [u8; Header::MAGIC.len()],
193 version: u16,
194}
195
196impl Header {
197 const MAGIC: &[u8] = b"Android-VM-instance";
198 const VERSION_1: u16 = 1;
199
200 pub fn is_valid(&self) -> bool {
201 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
202 }
203
204 fn version(&self) -> u16 {
205 u16::from_le(self.version)
206 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000207}
208
209fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000210 for transport in PciTransportIterator::<HalImpl>::new(pci_root)
211 .filter(|t| DeviceType::Block == t.device_type())
Alice Wang0e086232023-06-12 13:47:40 +0000212 {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000213 let device =
214 VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000215 match Partition::get_by_name(device, "vm-instance") {
216 Ok(Some(p)) => return Ok(p),
217 Ok(None) => {}
218 Err(e) => log::warn!("error while reading from disk: {e}"),
219 };
220 }
221
222 Err(Error::MissingInstanceImage)
223}
224
225#[derive(Debug)]
226enum PvmfwEntry {
227 Existing { header_index: usize, payload_size: usize },
228 New { header_index: usize },
229}
230
231const BLK_SIZE: usize = Partitions::LBA_SIZE;
232
233impl PvmfwEntry {
234 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
235}
236
237fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
238 let mut blk = [0; BLK_SIZE];
239 let mut indices = partition.indices();
240 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
241 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
242 // The instance.img header is only used for discovery/validation.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100243 let header = Header::read_from_prefix(blk.as_slice()).unwrap();
244 if !header.is_valid() {
245 return Err(Error::InvalidInstanceImageHeader);
246 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000247
248 while let Some(header_index) = indices.next() {
249 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
250
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100251 let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000252 match (header.uuid(), header.payload_size()) {
253 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
254 (PvmfwEntry::UUID, payload_size) => {
255 return Ok(PvmfwEntry::Existing { header_index, payload_size })
256 }
257 (uuid, payload_size) => {
258 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
259 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
260 if n > 0 {
261 let _ = indices.nth(n - 1); // consume
262 }
263 }
264 };
265 }
266
267 Err(Error::InstanceImageFull)
268}
269
270/// Marks the start of an instance.img entry.
271///
272/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
Frederick Mayle2e779942023-10-15 18:27:31 +0000273#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100274#[repr(C, packed)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000275struct EntryHeader {
276 uuid: u128,
277 payload_size: u64,
278}
279
280impl EntryHeader {
281 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100282 Self { uuid: uuid.to_u128_le(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000283 }
284
285 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100286 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000287 }
288
289 fn payload_size(&self) -> usize {
290 usize::try_from(u64::from_le(self.payload_size)).unwrap()
291 }
292}
293
Frederick Mayle2e779942023-10-15 18:27:31 +0000294#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000295#[repr(C)]
296struct EntryBody {
297 code_hash: Hash,
298 auth_hash: Hash,
299 salt: Hidden,
300 mode: u8,
301}
302
303impl EntryBody {
304 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
305 let mode = match dice_inputs.mode {
306 DiceMode::kDiceModeNotInitialized => 0,
307 DiceMode::kDiceModeNormal => 1,
308 DiceMode::kDiceModeDebug => 2,
309 DiceMode::kDiceModeMaintenance => 3,
310 };
311
312 Self {
313 code_hash: dice_inputs.code_hash,
314 auth_hash: dice_inputs.auth_hash,
315 salt: *salt,
316 mode,
317 }
318 }
319
320 fn mode(&self) -> DiceMode {
321 match self.mode {
322 1 => DiceMode::kDiceModeNormal,
323 2 => DiceMode::kDiceModeDebug,
324 3 => DiceMode::kDiceModeMaintenance,
325 _ => DiceMode::kDiceModeNotInitialized,
326 }
327 }
328}