blob: a974543e1a5eef57a6b3c1b81ec539a576770333 [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;
18use crate::crypto::hkdf_sh512;
19use crate::crypto::AeadCtx;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000020use crate::dice::PartialInputs;
21use crate::gpt;
22use crate::gpt::Partition;
23use crate::gpt::Partitions;
24use crate::helpers::ceiling_div;
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000025use crate::rand;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000026use crate::virtio::pci::VirtIOBlkIterator;
27use core::fmt;
28use core::mem::size_of;
29use core::slice;
30use diced_open_dice::DiceMode;
31use diced_open_dice::Hash;
32use diced_open_dice::Hidden;
33use log::trace;
34use uuid::Uuid;
35use virtio_drivers::transport::pci::bus::PciRoot;
36
37pub enum Error {
38 /// Unexpected I/O error while accessing the underlying disk.
39 FailedIo(gpt::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000040 /// Failed to decrypt the entry.
41 FailedOpen(crypto::ErrorIterator),
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000042 /// Failed to generate a random salt to be stored.
43 FailedSaltGeneration(rand::Error),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000044 /// Failed to encrypt the entry.
45 FailedSeal(crypto::ErrorIterator),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000046 /// Impossible to create a new instance.img entry.
47 InstanceImageFull,
48 /// Badly formatted instance.img header block.
49 InvalidInstanceImageHeader,
50 /// No instance.img ("vm-instance") partition found.
51 MissingInstanceImage,
52 /// The instance.img doesn't contain a header.
53 MissingInstanceImageHeader,
54 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
55 RecordedAuthHashMismatch,
56 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
57 RecordedCodeHashMismatch,
58 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
59 RecordedDiceModeMismatch,
60 /// Size of the instance.img entry being read or written is not supported.
61 UnsupportedEntrySize(usize),
62}
63
64impl fmt::Display for Error {
65 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 match self {
67 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000068 Self::FailedOpen(e_iter) => {
69 writeln!(f, "Failed to open the instance.img partition:")?;
70 for e in *e_iter {
71 writeln!(f, "\t{e}")?;
72 }
73 Ok(())
74 }
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000075 Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000076 Self::FailedSeal(e_iter) => {
77 writeln!(f, "Failed to seal the instance.img partition:")?;
78 for e in *e_iter {
79 writeln!(f, "\t{e}")?;
80 }
81 Ok(())
82 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000083 Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
84 Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
85 Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
86 Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
87 Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
88 Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
89 Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
90 Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
91 }
92 }
93}
94
95pub type Result<T> = core::result::Result<T, Error>;
96
97pub fn get_or_generate_instance_salt(
98 pci_root: &mut PciRoot,
99 dice_inputs: &PartialInputs,
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000100 secret: &[u8],
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000101) -> Result<(bool, Hidden)> {
102 let mut instance_img = find_instance_img(pci_root)?;
103
104 let entry = locate_entry(&mut instance_img)?;
105 trace!("Found pvmfw instance.img entry: {entry:?}");
106
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000107 let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000108 let mut blk = [0; BLK_SIZE];
109 match entry {
110 PvmfwEntry::Existing { header_index, payload_size } => {
111 if payload_size > blk.len() {
112 // We currently only support single-blk entries.
113 return Err(Error::UnsupportedEntrySize(payload_size));
114 }
115 let payload_index = header_index + 1;
116 instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
117
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000118 let payload = &blk[..payload_size];
119 let mut entry = [0; size_of::<EntryBody>()];
120 let key = key.map_err(Error::FailedOpen)?;
121 let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedOpen)?;
122 let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000123
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000124 let body: &EntryBody = decrypted.as_ref();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000125 if body.code_hash != dice_inputs.code_hash {
126 Err(Error::RecordedCodeHashMismatch)
127 } else if body.auth_hash != dice_inputs.auth_hash {
128 Err(Error::RecordedAuthHashMismatch)
129 } else if body.mode() != dice_inputs.mode {
130 Err(Error::RecordedDiceModeMismatch)
131 } else {
132 Ok((false, body.salt))
133 }
134 }
135 PvmfwEntry::New { header_index } => {
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +0000136 let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000137 let entry_body = EntryBody::new(dice_inputs, &salt);
138 let body = entry_body.as_ref();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000139
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000140 let key = key.map_err(Error::FailedSeal)?;
141 let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedSeal)?;
142 // We currently only support single-blk entries.
143 assert!(body.len() + aead.aead().unwrap().max_overhead() < blk.len());
144 let encrypted = aead.seal(&mut blk, body).map_err(Error::FailedSeal)?;
145 let payload_size = encrypted.len();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000146 let payload_index = header_index + 1;
147 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
148
149 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
150 let (blk_header, blk_rest) = blk.split_at_mut(size_of::<EntryHeader>());
151 blk_header.copy_from_slice(header.as_ref());
152 blk_rest.fill(0);
153 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
154
155 Ok((true, salt))
156 }
157 }
158}
159
160#[repr(C, packed)]
161struct Header {
162 magic: [u8; Header::MAGIC.len()],
163 version: u16,
164}
165
166impl Header {
167 const MAGIC: &[u8] = b"Android-VM-instance";
168 const VERSION_1: u16 = 1;
169
170 pub fn is_valid(&self) -> bool {
171 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
172 }
173
174 fn version(&self) -> u16 {
175 u16::from_le(self.version)
176 }
177
178 fn from_bytes(bytes: &[u8]) -> Option<&Self> {
179 let header: &Self = bytes.as_ref();
180
181 if header.is_valid() {
182 Some(header)
183 } else {
184 None
185 }
186 }
187}
188
189impl AsRef<Header> for [u8] {
190 fn as_ref(&self) -> &Header {
191 // SAFETY - Assume that the alignement and size match Header.
192 unsafe { &*self.as_ptr().cast::<Header>() }
193 }
194}
195
196fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
197 for device in VirtIOBlkIterator::new(pci_root) {
198 match Partition::get_by_name(device, "vm-instance") {
199 Ok(Some(p)) => return Ok(p),
200 Ok(None) => {}
201 Err(e) => log::warn!("error while reading from disk: {e}"),
202 };
203 }
204
205 Err(Error::MissingInstanceImage)
206}
207
208#[derive(Debug)]
209enum PvmfwEntry {
210 Existing { header_index: usize, payload_size: usize },
211 New { header_index: usize },
212}
213
214const BLK_SIZE: usize = Partitions::LBA_SIZE;
215
216impl PvmfwEntry {
217 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
218}
219
220fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
221 let mut blk = [0; BLK_SIZE];
222 let mut indices = partition.indices();
223 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
224 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
225 // The instance.img header is only used for discovery/validation.
226 let _ = Header::from_bytes(&blk).ok_or(Error::InvalidInstanceImageHeader)?;
227
228 while let Some(header_index) = indices.next() {
229 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
230
231 let header: &EntryHeader = blk[..size_of::<EntryHeader>()].as_ref();
232 match (header.uuid(), header.payload_size()) {
233 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
234 (PvmfwEntry::UUID, payload_size) => {
235 return Ok(PvmfwEntry::Existing { header_index, payload_size })
236 }
237 (uuid, payload_size) => {
238 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
239 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
240 if n > 0 {
241 let _ = indices.nth(n - 1); // consume
242 }
243 }
244 };
245 }
246
247 Err(Error::InstanceImageFull)
248}
249
250/// Marks the start of an instance.img entry.
251///
252/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
253#[repr(C)]
254struct EntryHeader {
255 uuid: u128,
256 payload_size: u64,
257}
258
259impl EntryHeader {
260 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100261 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 +0000262 }
263
264 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100265 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000266 }
267
268 fn payload_size(&self) -> usize {
269 usize::try_from(u64::from_le(self.payload_size)).unwrap()
270 }
271}
272
273impl AsRef<EntryHeader> for [u8] {
274 fn as_ref(&self) -> &EntryHeader {
275 assert_eq!(self.len(), size_of::<EntryHeader>());
276 // SAFETY - The size of the slice was checked and any value may be considered valid.
277 unsafe { &*self.as_ptr().cast::<EntryHeader>() }
278 }
279}
280
281impl AsRef<[u8]> for EntryHeader {
282 fn as_ref(&self) -> &[u8] {
283 let s = self as *const Self;
284 // SAFETY - Transmute the (valid) bytes into a slice.
285 unsafe { slice::from_raw_parts(s.cast::<u8>(), size_of::<Self>()) }
286 }
287}
288
289#[repr(C)]
290struct EntryBody {
291 code_hash: Hash,
292 auth_hash: Hash,
293 salt: Hidden,
294 mode: u8,
295}
296
297impl EntryBody {
298 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
299 let mode = match dice_inputs.mode {
300 DiceMode::kDiceModeNotInitialized => 0,
301 DiceMode::kDiceModeNormal => 1,
302 DiceMode::kDiceModeDebug => 2,
303 DiceMode::kDiceModeMaintenance => 3,
304 };
305
306 Self {
307 code_hash: dice_inputs.code_hash,
308 auth_hash: dice_inputs.auth_hash,
309 salt: *salt,
310 mode,
311 }
312 }
313
314 fn mode(&self) -> DiceMode {
315 match self.mode {
316 1 => DiceMode::kDiceModeNormal,
317 2 => DiceMode::kDiceModeDebug,
318 3 => DiceMode::kDiceModeMaintenance,
319 _ => DiceMode::kDiceModeNotInitialized,
320 }
321 }
322}
323
324impl AsRef<EntryBody> for [u8] {
325 fn as_ref(&self) -> &EntryBody {
326 assert_eq!(self.len(), size_of::<EntryBody>());
327 // SAFETY - The size of the slice was checked and members are validated by accessors.
328 unsafe { &*self.as_ptr().cast::<EntryBody>() }
329 }
330}
331
332impl AsRef<[u8]> for EntryBody {
333 fn as_ref(&self) -> &[u8] {
334 let s = self as *const Self;
335 // SAFETY - Transmute the (valid) bytes into a slice.
336 unsafe { slice::from_raw_parts(s.cast::<u8>(), size_of::<Self>()) }
337 }
338}