blob: 22839cbf29194dfee27a9c3db68b09d384236f69 [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;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000038
39pub enum Error {
40 /// Unexpected I/O error while accessing the underlying disk.
41 FailedIo(gpt::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000042 /// Failed to decrypt the entry.
43 FailedOpen(crypto::ErrorIterator),
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000044 /// Failed to generate a random salt to be stored.
45 FailedSaltGeneration(rand::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000046 /// Failed to encrypt the entry.
47 FailedSeal(crypto::ErrorIterator),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000048 /// Impossible to create a new instance.img entry.
49 InstanceImageFull,
50 /// Badly formatted instance.img header block.
51 InvalidInstanceImageHeader,
52 /// No instance.img ("vm-instance") partition found.
53 MissingInstanceImage,
54 /// The instance.img doesn't contain a header.
55 MissingInstanceImageHeader,
56 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
57 RecordedAuthHashMismatch,
58 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
59 RecordedCodeHashMismatch,
60 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
61 RecordedDiceModeMismatch,
62 /// Size of the instance.img entry being read or written is not supported.
63 UnsupportedEntrySize(usize),
Alice Wang0e086232023-06-12 13:47:40 +000064 /// Failed to create VirtIO Block device.
65 VirtIOBlkCreationFailed(virtio_drivers::Error),
Alice Wang947f3f72023-09-29 09:04:07 +000066 /// An error happened during the interaction with BoringSSL.
67 BoringSslFailed(bssl_avf::Error),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000068}
69
70impl fmt::Display for Error {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 match self {
73 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000074 Self::FailedOpen(e_iter) => {
75 writeln!(f, "Failed to open the instance.img partition:")?;
76 for e in *e_iter {
77 writeln!(f, "\t{e}")?;
78 }
79 Ok(())
80 }
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000081 Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000082 Self::FailedSeal(e_iter) => {
83 writeln!(f, "Failed to seal the instance.img partition:")?;
84 for e in *e_iter {
85 writeln!(f, "\t{e}")?;
86 }
87 Ok(())
88 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000089 Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
90 Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
91 Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
92 Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
93 Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
94 Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
95 Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
96 Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
Alice Wang0e086232023-06-12 13:47:40 +000097 Self::VirtIOBlkCreationFailed(e) => {
98 write!(f, "Failed to create VirtIO Block device: {e}")
99 }
Alice Wang947f3f72023-09-29 09:04:07 +0000100 Self::BoringSslFailed(e) => {
101 write!(f, "An error happened during the interaction with BoringSSL: {e}")
102 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000103 }
104 }
105}
106
Alice Wang947f3f72023-09-29 09:04:07 +0000107impl From<bssl_avf::Error> for Error {
108 fn from(e: bssl_avf::Error) -> Self {
109 Self::BoringSslFailed(e)
110 }
111}
112
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000113pub type Result<T> = core::result::Result<T, Error>;
114
115pub fn get_or_generate_instance_salt(
116 pci_root: &mut PciRoot,
117 dice_inputs: &PartialInputs,
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000118 secret: &[u8],
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000119) -> Result<(bool, Hidden)> {
120 let mut instance_img = find_instance_img(pci_root)?;
121
122 let entry = locate_entry(&mut instance_img)?;
123 trace!("Found pvmfw instance.img entry: {entry:?}");
124
Alice Wang947f3f72023-09-29 09:04:07 +0000125 let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000126 let mut blk = [0; BLK_SIZE];
127 match entry {
128 PvmfwEntry::Existing { header_index, payload_size } => {
129 if payload_size > blk.len() {
130 // We currently only support single-blk entries.
131 return Err(Error::UnsupportedEntrySize(payload_size));
132 }
133 let payload_index = header_index + 1;
134 instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
135
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000136 let payload = &blk[..payload_size];
137 let mut entry = [0; size_of::<EntryBody>()];
Alice Wangccc52e52023-10-03 13:14:09 +0000138 let aead =
139 AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedOpen)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000140 let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000141
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100142 let body = EntryBody::read_from(decrypted).unwrap();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000143 if body.code_hash != dice_inputs.code_hash {
144 Err(Error::RecordedCodeHashMismatch)
145 } else if body.auth_hash != dice_inputs.auth_hash {
146 Err(Error::RecordedAuthHashMismatch)
147 } else if body.mode() != dice_inputs.mode {
148 Err(Error::RecordedDiceModeMismatch)
149 } else {
150 Ok((false, body.salt))
151 }
152 }
153 PvmfwEntry::New { header_index } => {
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +0000154 let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100155 let body = EntryBody::new(dice_inputs, &salt);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000156
Alice Wangccc52e52023-10-03 13:14:09 +0000157 let aead =
158 AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000159 // We currently only support single-blk entries.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100160 let plaintext = body.as_bytes();
161 assert!(plaintext.len() + aead.aead().unwrap().max_overhead() < blk.len());
162 let encrypted = aead.seal(&mut blk, plaintext).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000163 let payload_size = encrypted.len();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000164 let payload_index = header_index + 1;
165 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
166
167 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100168 header.write_to_prefix(blk.as_mut_slice()).unwrap();
169 blk[header.as_bytes().len()..].fill(0);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000170 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
171
172 Ok((true, salt))
173 }
174 }
175}
176
Frederick Mayle893738e2023-10-15 03:50:34 +0000177#[derive(FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000178#[repr(C, packed)]
179struct Header {
180 magic: [u8; Header::MAGIC.len()],
181 version: u16,
182}
183
184impl Header {
185 const MAGIC: &[u8] = b"Android-VM-instance";
186 const VERSION_1: u16 = 1;
187
188 pub fn is_valid(&self) -> bool {
189 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
190 }
191
192 fn version(&self) -> u16 {
193 u16::from_le(self.version)
194 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000195}
196
197fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000198 for transport in PciTransportIterator::<HalImpl>::new(pci_root)
199 .filter(|t| DeviceType::Block == t.device_type())
Alice Wang0e086232023-06-12 13:47:40 +0000200 {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000201 let device =
202 VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000203 match Partition::get_by_name(device, "vm-instance") {
204 Ok(Some(p)) => return Ok(p),
205 Ok(None) => {}
206 Err(e) => log::warn!("error while reading from disk: {e}"),
207 };
208 }
209
210 Err(Error::MissingInstanceImage)
211}
212
213#[derive(Debug)]
214enum PvmfwEntry {
215 Existing { header_index: usize, payload_size: usize },
216 New { header_index: usize },
217}
218
219const BLK_SIZE: usize = Partitions::LBA_SIZE;
220
221impl PvmfwEntry {
222 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
223}
224
225fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
226 let mut blk = [0; BLK_SIZE];
227 let mut indices = partition.indices();
228 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
229 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
230 // The instance.img header is only used for discovery/validation.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100231 let header = Header::read_from_prefix(blk.as_slice()).unwrap();
232 if !header.is_valid() {
233 return Err(Error::InvalidInstanceImageHeader);
234 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000235
236 while let Some(header_index) = indices.next() {
237 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
238
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100239 let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000240 match (header.uuid(), header.payload_size()) {
241 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
242 (PvmfwEntry::UUID, payload_size) => {
243 return Ok(PvmfwEntry::Existing { header_index, payload_size })
244 }
245 (uuid, payload_size) => {
246 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
247 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
248 if n > 0 {
249 let _ = indices.nth(n - 1); // consume
250 }
251 }
252 };
253 }
254
255 Err(Error::InstanceImageFull)
256}
257
258/// Marks the start of an instance.img entry.
259///
260/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
Frederick Mayle893738e2023-10-15 03:50:34 +0000261#[derive(AsBytes, FromBytes)]
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100262#[repr(C, packed)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000263struct EntryHeader {
264 uuid: u128,
265 payload_size: u64,
266}
267
268impl EntryHeader {
269 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100270 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 +0000271 }
272
273 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100274 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000275 }
276
277 fn payload_size(&self) -> usize {
278 usize::try_from(u64::from_le(self.payload_size)).unwrap()
279 }
280}
281
Frederick Mayle893738e2023-10-15 03:50:34 +0000282#[derive(AsBytes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000283#[repr(C)]
284struct EntryBody {
285 code_hash: Hash,
286 auth_hash: Hash,
287 salt: Hidden,
288 mode: u8,
289}
290
291impl EntryBody {
292 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
293 let mode = match dice_inputs.mode {
294 DiceMode::kDiceModeNotInitialized => 0,
295 DiceMode::kDiceModeNormal => 1,
296 DiceMode::kDiceModeDebug => 2,
297 DiceMode::kDiceModeMaintenance => 3,
298 };
299
300 Self {
301 code_hash: dice_inputs.code_hash,
302 auth_hash: dice_inputs.auth_hash,
303 salt: *salt,
304 mode,
305 }
306 }
307
308 fn mode(&self) -> DiceMode {
309 match self.mode {
310 1 => DiceMode::kDiceModeNormal,
311 2 => DiceMode::kDiceModeDebug,
312 3 => DiceMode::kDiceModeMaintenance,
313 _ => DiceMode::kDiceModeNotInitialized,
314 }
315 }
316}