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