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::dice::PartialInputs;
18 use crate::gpt;
19 use crate::gpt::Partition;
20 use crate::gpt::Partitions;
21 use bssl_avf::{self, hkdf, Aead, AeadContext, Digester};
22 use core::fmt;
23 use core::mem::size_of;
24 use diced_open_dice::DiceMode;
25 use diced_open_dice::Hash;
26 use diced_open_dice::Hidden;
27 use log::trace;
28 use uuid::Uuid;
29 use virtio_drivers::transport::{pci::bus::PciRoot, DeviceType, Transport};
30 use vmbase::util::ceiling_div;
31 use vmbase::virtio::pci::{PciTransportIterator, VirtIOBlk};
32 use vmbase::virtio::HalImpl;
33 use zerocopy::AsBytes;
34 use zerocopy::FromBytes;
35 use zerocopy::FromZeroes;
36 
37 pub enum Error {
38     /// Unexpected I/O error while accessing the underlying disk.
39     FailedIo(gpt::Error),
40     /// Impossible to create a new instance.img entry.
41     InstanceImageFull,
42     /// Badly formatted instance.img header block.
43     InvalidInstanceImageHeader,
44     /// No instance.img ("vm-instance") partition found.
45     MissingInstanceImage,
46     /// The instance.img doesn't contain a header.
47     MissingInstanceImageHeader,
48     /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
49     RecordedAuthHashMismatch,
50     /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
51     RecordedCodeHashMismatch,
52     /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
53     RecordedDiceModeMismatch,
54     /// Size of the instance.img entry being read or written is not supported.
55     UnsupportedEntrySize(usize),
56     /// Failed to create VirtIO Block device.
57     VirtIOBlkCreationFailed(virtio_drivers::Error),
58     /// An error happened during the interaction with BoringSSL.
59     BoringSslFailed(bssl_avf::Error),
60 }
61 
62 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result63     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64         match self {
65             Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
66             Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
67             Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
68             Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
69             Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
70             Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
71             Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
72             Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
73             Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
74             Self::VirtIOBlkCreationFailed(e) => {
75                 write!(f, "Failed to create VirtIO Block device: {e}")
76             }
77             Self::BoringSslFailed(e) => {
78                 write!(f, "An error happened during the interaction with BoringSSL: {e}")
79             }
80         }
81     }
82 }
83 
84 impl From<bssl_avf::Error> for Error {
from(e: bssl_avf::Error) -> Self85     fn from(e: bssl_avf::Error) -> Self {
86         Self::BoringSslFailed(e)
87     }
88 }
89 
90 pub type Result<T> = core::result::Result<T, Error>;
91 
aead_ctx_from_secret(secret: &[u8]) -> Result<AeadContext>92 fn aead_ctx_from_secret(secret: &[u8]) -> Result<AeadContext> {
93     let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
94     Ok(AeadContext::new(Aead::aes_256_gcm_randnonce(), key.as_slice(), /* tag_len */ None)?)
95 }
96 
97 /// Get the entry from instance.img. This method additionally returns Partition corresponding to
98 /// pvmfw in the instance.img as well as index corresponding to empty header which can be used to
99 /// record instance data with `record_instance_entry`.
get_recorded_entry( pci_root: &mut PciRoot, secret: &[u8], ) -> Result<(Option<EntryBody>, Partition, usize)>100 pub(crate) fn get_recorded_entry(
101     pci_root: &mut PciRoot,
102     secret: &[u8],
103 ) -> Result<(Option<EntryBody>, Partition, usize)> {
104     let mut instance_img = find_instance_img(pci_root)?;
105 
106     let entry = locate_entry(&mut instance_img)?;
107     trace!("Found pvmfw instance.img entry: {entry:?}");
108 
109     match entry {
110         PvmfwEntry::Existing { header_index, payload_size } => {
111             let aead_ctx = aead_ctx_from_secret(secret)?;
112             let mut blk = [0; BLK_SIZE];
113             if payload_size > blk.len() {
114                 // We currently only support single-blk entries.
115                 return Err(Error::UnsupportedEntrySize(payload_size));
116             }
117             let payload_index = header_index + 1;
118             instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
119 
120             let payload = &blk[..payload_size];
121             let mut entry = [0; size_of::<EntryBody>()];
122             // The nonce is generated internally for `aes_256_gcm_randnonce`, so no additional
123             // nonce is required.
124             let decrypted =
125                 aead_ctx.open(payload, /* nonce */ &[], /* ad */ &[], &mut entry)?;
126             let body = EntryBody::read_from(decrypted).unwrap();
127             Ok((Some(body), instance_img, header_index))
128         }
129         PvmfwEntry::New { header_index } => Ok((None, instance_img, header_index)),
130     }
131 }
132 
record_instance_entry( body: &EntryBody, secret: &[u8], instance_img: &mut Partition, header_index: usize, ) -> Result<()>133 pub(crate) fn record_instance_entry(
134     body: &EntryBody,
135     secret: &[u8],
136     instance_img: &mut Partition,
137     header_index: usize,
138 ) -> Result<()> {
139     // We currently only support single-blk entries.
140     let mut blk = [0; BLK_SIZE];
141     let plaintext = body.as_bytes();
142     let aead_ctx = aead_ctx_from_secret(secret)?;
143     assert!(plaintext.len() + aead_ctx.aead().max_overhead() < blk.len());
144     let encrypted = aead_ctx.seal(plaintext, /* nonce */ &[], /* ad */ &[], &mut blk)?;
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     header.write_to_prefix(blk.as_mut_slice()).unwrap();
151     blk[header.as_bytes().len()..].fill(0);
152     instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
153 
154     Ok(())
155 }
156 
157 #[derive(FromZeroes, FromBytes)]
158 #[repr(C, packed)]
159 struct Header {
160     magic: [u8; Header::MAGIC.len()],
161     version: u16,
162 }
163 
164 impl Header {
165     const MAGIC: &'static [u8] = b"Android-VM-instance";
166     const VERSION_1: u16 = 1;
167 
is_valid(&self) -> bool168     pub fn is_valid(&self) -> bool {
169         self.magic == Self::MAGIC && self.version() == Self::VERSION_1
170     }
171 
version(&self) -> u16172     fn version(&self) -> u16 {
173         u16::from_le(self.version)
174     }
175 }
176 
find_instance_img(pci_root: &mut PciRoot) -> Result<Partition>177 fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
178     for transport in PciTransportIterator::<HalImpl>::new(pci_root)
179         .filter(|t| DeviceType::Block == t.device_type())
180     {
181         let device =
182             VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
183         match Partition::get_by_name(device, "vm-instance") {
184             Ok(Some(p)) => return Ok(p),
185             Ok(None) => {}
186             Err(e) => log::warn!("error while reading from disk: {e}"),
187         };
188     }
189 
190     Err(Error::MissingInstanceImage)
191 }
192 
193 #[derive(Debug)]
194 enum PvmfwEntry {
195     Existing { header_index: usize, payload_size: usize },
196     New { header_index: usize },
197 }
198 
199 const BLK_SIZE: usize = Partitions::LBA_SIZE;
200 
201 impl PvmfwEntry {
202     const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
203 }
204 
locate_entry(partition: &mut Partition) -> Result<PvmfwEntry>205 fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
206     let mut blk = [0; BLK_SIZE];
207     let mut indices = partition.indices();
208     let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
209     partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
210     // The instance.img header is only used for discovery/validation.
211     let header = Header::read_from_prefix(blk.as_slice()).unwrap();
212     if !header.is_valid() {
213         return Err(Error::InvalidInstanceImageHeader);
214     }
215 
216     while let Some(header_index) = indices.next() {
217         partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
218 
219         let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap();
220         match (header.uuid(), header.payload_size()) {
221             (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
222             (PvmfwEntry::UUID, payload_size) => {
223                 return Ok(PvmfwEntry::Existing { header_index, payload_size })
224             }
225             (uuid, payload_size) => {
226                 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
227                 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
228                 if n > 0 {
229                     let _ = indices.nth(n - 1); // consume
230                 }
231             }
232         };
233     }
234 
235     Err(Error::InstanceImageFull)
236 }
237 
238 /// Marks the start of an instance.img entry.
239 ///
240 /// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
241 #[derive(AsBytes, FromZeroes, FromBytes)]
242 #[repr(C, packed)]
243 struct EntryHeader {
244     uuid: u128,
245     payload_size: u64,
246 }
247 
248 impl EntryHeader {
new(uuid: Uuid, payload_size: usize) -> Self249     fn new(uuid: Uuid, payload_size: usize) -> Self {
250         Self { uuid: uuid.to_u128_le(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
251     }
252 
uuid(&self) -> Uuid253     fn uuid(&self) -> Uuid {
254         Uuid::from_u128_le(self.uuid)
255     }
256 
payload_size(&self) -> usize257     fn payload_size(&self) -> usize {
258         usize::try_from(u64::from_le(self.payload_size)).unwrap()
259     }
260 }
261 
262 #[derive(AsBytes, FromZeroes, FromBytes)]
263 #[repr(C)]
264 pub(crate) struct EntryBody {
265     pub code_hash: Hash,
266     pub auth_hash: Hash,
267     pub salt: Hidden,
268     mode: u8,
269 }
270 
271 impl EntryBody {
new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self272     pub(crate) fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
273         let mode = match dice_inputs.mode {
274             DiceMode::kDiceModeNotInitialized => 0,
275             DiceMode::kDiceModeNormal => 1,
276             DiceMode::kDiceModeDebug => 2,
277             DiceMode::kDiceModeMaintenance => 3,
278         };
279 
280         Self {
281             code_hash: dice_inputs.code_hash,
282             auth_hash: dice_inputs.auth_hash,
283             salt: *salt,
284             mode,
285         }
286     }
287 
mode(&self) -> DiceMode288     pub(crate) fn mode(&self) -> DiceMode {
289         match self.mode {
290             1 => DiceMode::kDiceModeNormal,
291             2 => DiceMode::kDiceModeDebug,
292             3 => DiceMode::kDiceModeMaintenance,
293             _ => DiceMode::kDiceModeNotInitialized,
294         }
295     }
296 }
297