blob: bb07f74befbba3b675df27c322b671ad4c5ac446 [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
17use crate::dice::PartialInputs;
18use crate::gpt;
19use crate::gpt::Partition;
20use crate::gpt::Partitions;
Alice Wangee07f722023-10-03 15:20:17 +000021use bssl_avf::{self, hkdf, Aead, AeadContext, Digester};
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000022use core::fmt;
23use core::mem::size_of;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000024use diced_open_dice::DiceMode;
25use diced_open_dice::Hash;
26use diced_open_dice::Hidden;
27use log::trace;
28use uuid::Uuid;
Alice Wang0e086232023-06-12 13:47:40 +000029use virtio_drivers::transport::{pci::bus::PciRoot, DeviceType, Transport};
Alice Wangeacb7382023-06-05 12:53:54 +000030use vmbase::util::ceiling_div;
Alice Wang0e086232023-06-12 13:47:40 +000031use vmbase::virtio::pci::{PciTransportIterator, VirtIOBlk};
Alice Wang7c55c7d2023-07-05 14:51:40 +000032use vmbase::virtio::HalImpl;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +010033use zerocopy::FromBytes;
Andrew Walbran47d316e2024-11-28 18:41:09 +000034use zerocopy::Immutable;
35use zerocopy::IntoBytes;
36use zerocopy::KnownLayout;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000037
38pub enum Error {
39 /// Unexpected I/O error while accessing the underlying disk.
40 FailedIo(gpt::Error),
41 /// Impossible to create a new instance.img entry.
42 InstanceImageFull,
43 /// Badly formatted instance.img header block.
44 InvalidInstanceImageHeader,
45 /// No instance.img ("vm-instance") partition found.
46 MissingInstanceImage,
47 /// The instance.img doesn't contain a header.
48 MissingInstanceImageHeader,
49 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
50 RecordedAuthHashMismatch,
51 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
52 RecordedCodeHashMismatch,
53 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
54 RecordedDiceModeMismatch,
55 /// Size of the instance.img entry being read or written is not supported.
56 UnsupportedEntrySize(usize),
Alice Wang0e086232023-06-12 13:47:40 +000057 /// Failed to create VirtIO Block device.
58 VirtIOBlkCreationFailed(virtio_drivers::Error),
Alice Wang947f3f72023-09-29 09:04:07 +000059 /// An error happened during the interaction with BoringSSL.
60 BoringSslFailed(bssl_avf::Error),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000061}
62
63impl fmt::Display for Error {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 match self {
66 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
67 Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
68 Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
69 Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
70 Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
71 Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
72 Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
73 Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
74 Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
Alice Wang0e086232023-06-12 13:47:40 +000075 Self::VirtIOBlkCreationFailed(e) => {
76 write!(f, "Failed to create VirtIO Block device: {e}")
77 }
Alice Wang947f3f72023-09-29 09:04:07 +000078 Self::BoringSslFailed(e) => {
79 write!(f, "An error happened during the interaction with BoringSSL: {e}")
80 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000081 }
82 }
83}
84
Alice Wang947f3f72023-09-29 09:04:07 +000085impl From<bssl_avf::Error> for Error {
86 fn from(e: bssl_avf::Error) -> Self {
87 Self::BoringSslFailed(e)
88 }
89}
90
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000091pub type Result<T> = core::result::Result<T, Error>;
92
Shikha Panwar37490d42024-03-19 22:14:58 +000093fn aead_ctx_from_secret(secret: &[u8]) -> Result<AeadContext> {
94 let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
95 Ok(AeadContext::new(Aead::aes_256_gcm_randnonce(), key.as_slice(), /* tag_len */ None)?)
96}
97
98/// Get the entry from instance.img. This method additionally returns Partition corresponding to
99/// pvmfw in the instance.img as well as index corresponding to empty header which can be used to
100/// record instance data with `record_instance_entry`.
101pub(crate) fn get_recorded_entry(
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000102 pci_root: &mut PciRoot,
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000103 secret: &[u8],
Shikha Panwar37490d42024-03-19 22:14:58 +0000104) -> Result<(Option<EntryBody>, Partition, usize)> {
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000105 let mut instance_img = find_instance_img(pci_root)?;
106
107 let entry = locate_entry(&mut instance_img)?;
108 trace!("Found pvmfw instance.img entry: {entry:?}");
109
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000110 match entry {
111 PvmfwEntry::Existing { header_index, payload_size } => {
Shikha Panwar37490d42024-03-19 22:14:58 +0000112 let aead_ctx = aead_ctx_from_secret(secret)?;
113 let mut blk = [0; BLK_SIZE];
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000114 if payload_size > blk.len() {
115 // We currently only support single-blk entries.
116 return Err(Error::UnsupportedEntrySize(payload_size));
117 }
118 let payload_index = header_index + 1;
119 instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
120
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000121 let payload = &blk[..payload_size];
122 let mut entry = [0; size_of::<EntryBody>()];
Shikha Panwar37490d42024-03-19 22:14:58 +0000123 // The nonce is generated internally for `aes_256_gcm_randnonce`, so no additional
124 // nonce is required.
125 let decrypted =
126 aead_ctx.open(payload, /* nonce */ &[], /* ad */ &[], &mut entry)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100127 let body = EntryBody::read_from(decrypted).unwrap();
Shikha Panwar37490d42024-03-19 22:14:58 +0000128 Ok((Some(body), instance_img, header_index))
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000129 }
Shikha Panwar37490d42024-03-19 22:14:58 +0000130 PvmfwEntry::New { header_index } => Ok((None, instance_img, header_index)),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000131 }
132}
133
Shikha Panwar37490d42024-03-19 22:14:58 +0000134pub(crate) fn record_instance_entry(
135 body: &EntryBody,
136 secret: &[u8],
137 instance_img: &mut Partition,
138 header_index: usize,
139) -> Result<()> {
140 // We currently only support single-blk entries.
141 let mut blk = [0; BLK_SIZE];
142 let plaintext = body.as_bytes();
143 let aead_ctx = aead_ctx_from_secret(secret)?;
144 assert!(plaintext.len() + aead_ctx.aead().max_overhead() < blk.len());
145 let encrypted = aead_ctx.seal(plaintext, /* nonce */ &[], /* ad */ &[], &mut blk)?;
146 let payload_size = encrypted.len();
147 let payload_index = header_index + 1;
148 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
149
150 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
151 header.write_to_prefix(blk.as_mut_slice()).unwrap();
152 blk[header.as_bytes().len()..].fill(0);
153 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
154
155 Ok(())
156}
157
Andrew Walbran47d316e2024-11-28 18:41:09 +0000158#[derive(Clone, Debug, FromBytes, Immutable, KnownLayout)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000159#[repr(C, packed)]
160struct Header {
161 magic: [u8; Header::MAGIC.len()],
162 version: u16,
163}
164
165impl Header {
Chris Wailes9d09f572024-01-16 13:31:02 -0800166 const MAGIC: &'static [u8] = b"Android-VM-instance";
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000167 const VERSION_1: u16 = 1;
168
169 pub fn is_valid(&self) -> bool {
170 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
171 }
172
173 fn version(&self) -> u16 {
174 u16::from_le(self.version)
175 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000176}
177
178fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000179 for transport in PciTransportIterator::<HalImpl>::new(pci_root)
180 .filter(|t| DeviceType::Block == t.device_type())
Alice Wang0e086232023-06-12 13:47:40 +0000181 {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000182 let device =
183 VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000184 match Partition::get_by_name(device, "vm-instance") {
185 Ok(Some(p)) => return Ok(p),
186 Ok(None) => {}
187 Err(e) => log::warn!("error while reading from disk: {e}"),
188 };
189 }
190
191 Err(Error::MissingInstanceImage)
192}
193
194#[derive(Debug)]
195enum PvmfwEntry {
196 Existing { header_index: usize, payload_size: usize },
197 New { header_index: usize },
198}
199
200const BLK_SIZE: usize = Partitions::LBA_SIZE;
201
202impl PvmfwEntry {
203 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
204}
205
206fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
207 let mut blk = [0; BLK_SIZE];
208 let mut indices = partition.indices();
209 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
210 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
211 // The instance.img header is only used for discovery/validation.
Andrew Walbran47d316e2024-11-28 18:41:09 +0000212 let header = Header::read_from_prefix(blk.as_slice()).unwrap().0;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100213 if !header.is_valid() {
214 return Err(Error::InvalidInstanceImageHeader);
215 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000216
217 while let Some(header_index) = indices.next() {
218 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
219
Andrew Walbran47d316e2024-11-28 18:41:09 +0000220 let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap().0;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000221 match (header.uuid(), header.payload_size()) {
222 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
223 (PvmfwEntry::UUID, payload_size) => {
224 return Ok(PvmfwEntry::Existing { header_index, payload_size })
225 }
226 (uuid, payload_size) => {
227 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
228 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
229 if n > 0 {
230 let _ = indices.nth(n - 1); // consume
231 }
232 }
233 };
234 }
235
236 Err(Error::InstanceImageFull)
237}
238
239/// Marks the start of an instance.img entry.
240///
Jiyong Park7ec05d02024-07-22 12:26:04 +0900241/// Note: Virtualization/guest/microdroid_manager/src/instance.rs uses the name "partition".
Andrew Walbran47d316e2024-11-28 18:41:09 +0000242#[derive(Clone, Debug, FromBytes, Immutable, IntoBytes, KnownLayout)]
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100243#[repr(C, packed)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000244struct EntryHeader {
245 uuid: u128,
246 payload_size: u64,
247}
248
249impl EntryHeader {
250 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100251 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 +0000252 }
253
254 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100255 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000256 }
257
258 fn payload_size(&self) -> usize {
259 usize::try_from(u64::from_le(self.payload_size)).unwrap()
260 }
261}
262
Andrew Walbran47d316e2024-11-28 18:41:09 +0000263#[derive(Clone, Debug, FromBytes, Immutable, IntoBytes, KnownLayout)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000264#[repr(C)]
Shikha Panwar37490d42024-03-19 22:14:58 +0000265pub(crate) struct EntryBody {
266 pub code_hash: Hash,
267 pub auth_hash: Hash,
268 pub salt: Hidden,
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000269 mode: u8,
270}
271
272impl EntryBody {
Shikha Panwar37490d42024-03-19 22:14:58 +0000273 pub(crate) fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000274 let mode = match dice_inputs.mode {
275 DiceMode::kDiceModeNotInitialized => 0,
276 DiceMode::kDiceModeNormal => 1,
277 DiceMode::kDiceModeDebug => 2,
278 DiceMode::kDiceModeMaintenance => 3,
279 };
280
281 Self {
282 code_hash: dice_inputs.code_hash,
283 auth_hash: dice_inputs.auth_hash,
284 salt: *salt,
285 mode,
286 }
287 }
288
Shikha Panwar37490d42024-03-19 22:14:58 +0000289 pub(crate) fn mode(&self) -> DiceMode {
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000290 match self.mode {
291 1 => DiceMode::kDiceModeNormal,
292 2 => DiceMode::kDiceModeDebug,
293 3 => DiceMode::kDiceModeMaintenance,
294 _ => DiceMode::kDiceModeNotInitialized,
295 }
296 }
297}