• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // 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 
17 use crate::crypto;
18 use crate::crypto::hkdf_sh512;
19 use crate::crypto::AeadCtx;
20 use crate::dice::PartialInputs;
21 use crate::gpt;
22 use crate::gpt::Partition;
23 use crate::gpt::Partitions;
24 use crate::helpers::ceiling_div;
25 use crate::rand;
26 use crate::virtio::pci::VirtIOBlkIterator;
27 use core::fmt;
28 use core::mem::size_of;
29 use core::slice;
30 use diced_open_dice::DiceMode;
31 use diced_open_dice::Hash;
32 use diced_open_dice::Hidden;
33 use log::trace;
34 use uuid::Uuid;
35 use virtio_drivers::transport::pci::bus::PciRoot;
36 
37 pub enum Error {
38     /// Unexpected I/O error while accessing the underlying disk.
39     FailedIo(gpt::Error),
40     /// Failed to decrypt the entry.
41     FailedOpen(crypto::ErrorIterator),
42     /// Failed to generate a random salt to be stored.
43     FailedSaltGeneration(rand::Error),
44     /// Failed to encrypt the entry.
45     FailedSeal(crypto::ErrorIterator),
46     /// 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 
64 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result65     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66         match self {
67             Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
68             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             }
75             Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
76             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             }
83             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 
95 pub type Result<T> = core::result::Result<T, Error>;
96 
get_or_generate_instance_salt( pci_root: &mut PciRoot, dice_inputs: &PartialInputs, secret: &[u8], ) -> Result<(bool, Hidden)>97 pub fn get_or_generate_instance_salt(
98     pci_root: &mut PciRoot,
99     dice_inputs: &PartialInputs,
100     secret: &[u8],
101 ) -> 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 
107     let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
108     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 
118             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)?;
123 
124             let body: &EntryBody = decrypted.as_ref();
125             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 } => {
136             let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
137             let entry_body = EntryBody::new(dice_inputs, &salt);
138             let body = entry_body.as_ref();
139 
140             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();
146             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)]
161 struct Header {
162     magic: [u8; Header::MAGIC.len()],
163     version: u16,
164 }
165 
166 impl Header {
167     const MAGIC: &[u8] = b"Android-VM-instance";
168     const VERSION_1: u16 = 1;
169 
is_valid(&self) -> bool170     pub fn is_valid(&self) -> bool {
171         self.magic == Self::MAGIC && self.version() == Self::VERSION_1
172     }
173 
version(&self) -> u16174     fn version(&self) -> u16 {
175         u16::from_le(self.version)
176     }
177 
from_bytes(bytes: &[u8]) -> Option<&Self>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 
189 impl AsRef<Header> for [u8] {
as_ref(&self) -> &Header190     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 
find_instance_img(pci_root: &mut PciRoot) -> Result<Partition>196 fn 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)]
209 enum PvmfwEntry {
210     Existing { header_index: usize, payload_size: usize },
211     New { header_index: usize },
212 }
213 
214 const BLK_SIZE: usize = Partitions::LBA_SIZE;
215 
216 impl PvmfwEntry {
217     const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
218 }
219 
locate_entry(partition: &mut Partition) -> Result<PvmfwEntry>220 fn 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)]
254 struct EntryHeader {
255     uuid: u128,
256     payload_size: u64,
257 }
258 
259 impl EntryHeader {
new(uuid: Uuid, payload_size: usize) -> Self260     fn new(uuid: Uuid, payload_size: usize) -> Self {
261         Self { uuid: uuid.to_u128_le(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
262     }
263 
uuid(&self) -> Uuid264     fn uuid(&self) -> Uuid {
265         Uuid::from_u128_le(self.uuid)
266     }
267 
payload_size(&self) -> usize268     fn payload_size(&self) -> usize {
269         usize::try_from(u64::from_le(self.payload_size)).unwrap()
270     }
271 }
272 
273 impl AsRef<EntryHeader> for [u8] {
as_ref(&self) -> &EntryHeader274     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 
281 impl AsRef<[u8]> for EntryHeader {
as_ref(&self) -> &[u8]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)]
290 struct EntryBody {
291     code_hash: Hash,
292     auth_hash: Hash,
293     salt: Hidden,
294     mode: u8,
295 }
296 
297 impl EntryBody {
new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self298     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 
mode(&self) -> DiceMode314     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 
324 impl AsRef<EntryBody> for [u8] {
as_ref(&self) -> &EntryBody325     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 
332 impl AsRef<[u8]> for EntryBody {
as_ref(&self) -> &[u8]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 }
339