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