blob: f2cd6a341fe0b70f63000e34bff4c78fa7bcc9c5 [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();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000144 if body.code_hash != dice_inputs.code_hash {
145 Err(Error::RecordedCodeHashMismatch)
146 } else if body.auth_hash != dice_inputs.auth_hash {
147 Err(Error::RecordedAuthHashMismatch)
148 } else if body.mode() != dice_inputs.mode {
149 Err(Error::RecordedDiceModeMismatch)
150 } else {
151 Ok((false, body.salt))
152 }
153 }
154 PvmfwEntry::New { header_index } => {
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +0000155 let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100156 let body = EntryBody::new(dice_inputs, &salt);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000157
Alice Wangccc52e52023-10-03 13:14:09 +0000158 let aead =
159 AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000160 // We currently only support single-blk entries.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100161 let plaintext = body.as_bytes();
162 assert!(plaintext.len() + aead.aead().unwrap().max_overhead() < blk.len());
163 let encrypted = aead.seal(&mut blk, plaintext).map_err(Error::FailedSeal)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000164 let payload_size = encrypted.len();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000165 let payload_index = header_index + 1;
166 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
167
168 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100169 header.write_to_prefix(blk.as_mut_slice()).unwrap();
170 blk[header.as_bytes().len()..].fill(0);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000171 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
172
173 Ok((true, salt))
174 }
175 }
176}
177
Frederick Mayle2e779942023-10-15 18:27:31 +0000178#[derive(FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000179#[repr(C, packed)]
180struct Header {
181 magic: [u8; Header::MAGIC.len()],
182 version: u16,
183}
184
185impl Header {
186 const MAGIC: &[u8] = b"Android-VM-instance";
187 const VERSION_1: u16 = 1;
188
189 pub fn is_valid(&self) -> bool {
190 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
191 }
192
193 fn version(&self) -> u16 {
194 u16::from_le(self.version)
195 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000196}
197
198fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000199 for transport in PciTransportIterator::<HalImpl>::new(pci_root)
200 .filter(|t| DeviceType::Block == t.device_type())
Alice Wang0e086232023-06-12 13:47:40 +0000201 {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000202 let device =
203 VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000204 match Partition::get_by_name(device, "vm-instance") {
205 Ok(Some(p)) => return Ok(p),
206 Ok(None) => {}
207 Err(e) => log::warn!("error while reading from disk: {e}"),
208 };
209 }
210
211 Err(Error::MissingInstanceImage)
212}
213
214#[derive(Debug)]
215enum PvmfwEntry {
216 Existing { header_index: usize, payload_size: usize },
217 New { header_index: usize },
218}
219
220const BLK_SIZE: usize = Partitions::LBA_SIZE;
221
222impl PvmfwEntry {
223 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
224}
225
226fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
227 let mut blk = [0; BLK_SIZE];
228 let mut indices = partition.indices();
229 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
230 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
231 // The instance.img header is only used for discovery/validation.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100232 let header = Header::read_from_prefix(blk.as_slice()).unwrap();
233 if !header.is_valid() {
234 return Err(Error::InvalidInstanceImageHeader);
235 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000236
237 while let Some(header_index) = indices.next() {
238 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
239
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100240 let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000241 match (header.uuid(), header.payload_size()) {
242 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
243 (PvmfwEntry::UUID, payload_size) => {
244 return Ok(PvmfwEntry::Existing { header_index, payload_size })
245 }
246 (uuid, payload_size) => {
247 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
248 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
249 if n > 0 {
250 let _ = indices.nth(n - 1); // consume
251 }
252 }
253 };
254 }
255
256 Err(Error::InstanceImageFull)
257}
258
259/// Marks the start of an instance.img entry.
260///
261/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
Frederick Mayle2e779942023-10-15 18:27:31 +0000262#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100263#[repr(C, packed)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000264struct EntryHeader {
265 uuid: u128,
266 payload_size: u64,
267}
268
269impl EntryHeader {
270 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100271 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 +0000272 }
273
274 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100275 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000276 }
277
278 fn payload_size(&self) -> usize {
279 usize::try_from(u64::from_le(self.payload_size)).unwrap()
280 }
281}
282
Frederick Mayle2e779942023-10-15 18:27:31 +0000283#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000284#[repr(C)]
285struct EntryBody {
286 code_hash: Hash,
287 auth_hash: Hash,
288 salt: Hidden,
289 mode: u8,
290}
291
292impl EntryBody {
293 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
294 let mode = match dice_inputs.mode {
295 DiceMode::kDiceModeNotInitialized => 0,
296 DiceMode::kDiceModeNormal => 1,
297 DiceMode::kDiceModeDebug => 2,
298 DiceMode::kDiceModeMaintenance => 3,
299 };
300
301 Self {
302 code_hash: dice_inputs.code_hash,
303 auth_hash: dice_inputs.auth_hash,
304 salt: *salt,
305 mode,
306 }
307 }
308
309 fn mode(&self) -> DiceMode {
310 match self.mode {
311 1 => DiceMode::kDiceModeNormal,
312 2 => DiceMode::kDiceModeDebug,
313 3 => DiceMode::kDiceModeMaintenance,
314 _ => DiceMode::kDiceModeNotInitialized,
315 }
316 }
317}