blob: 6daadd91e99281697bb05bfa8a76c1121e58767e [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};
Pierre-Clément Tosi3e3d7332023-06-22 10:44:29 +000030use vmbase::rand;
Alice Wangeacb7382023-06-05 12:53:54 +000031use vmbase::util::ceiling_div;
Alice Wang0e086232023-06-12 13:47:40 +000032use vmbase::virtio::pci::{PciTransportIterator, VirtIOBlk};
Alice Wang7c55c7d2023-07-05 14:51:40 +000033use vmbase::virtio::HalImpl;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +010034use zerocopy::AsBytes;
35use zerocopy::FromBytes;
Frederick Mayle2e779942023-10-15 18:27:31 +000036use zerocopy::FromZeroes;
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),
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000041 /// Failed to generate a random salt to be stored.
42 FailedSaltGeneration(rand::Error),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000043 /// Impossible to create a new instance.img entry.
44 InstanceImageFull,
45 /// Badly formatted instance.img header block.
46 InvalidInstanceImageHeader,
47 /// No instance.img ("vm-instance") partition found.
48 MissingInstanceImage,
49 /// The instance.img doesn't contain a header.
50 MissingInstanceImageHeader,
51 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
52 RecordedAuthHashMismatch,
53 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
54 RecordedCodeHashMismatch,
55 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
56 RecordedDiceModeMismatch,
57 /// Size of the instance.img entry being read or written is not supported.
58 UnsupportedEntrySize(usize),
Alice Wang0e086232023-06-12 13:47:40 +000059 /// Failed to create VirtIO Block device.
60 VirtIOBlkCreationFailed(virtio_drivers::Error),
Alice Wang947f3f72023-09-29 09:04:07 +000061 /// An error happened during the interaction with BoringSSL.
62 BoringSslFailed(bssl_avf::Error),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000063}
64
65impl fmt::Display for Error {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 match self {
68 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +000069 Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000070 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}"),
Alice Wang0e086232023-06-12 13:47:40 +000078 Self::VirtIOBlkCreationFailed(e) => {
79 write!(f, "Failed to create VirtIO Block device: {e}")
80 }
Alice Wang947f3f72023-09-29 09:04:07 +000081 Self::BoringSslFailed(e) => {
82 write!(f, "An error happened during the interaction with BoringSSL: {e}")
83 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000084 }
85 }
86}
87
Alice Wang947f3f72023-09-29 09:04:07 +000088impl From<bssl_avf::Error> for Error {
89 fn from(e: bssl_avf::Error) -> Self {
90 Self::BoringSslFailed(e)
91 }
92}
93
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +000094pub type Result<T> = core::result::Result<T, Error>;
95
96pub fn get_or_generate_instance_salt(
97 pci_root: &mut PciRoot,
98 dice_inputs: &PartialInputs,
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +000099 secret: &[u8],
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000100) -> Result<(bool, Hidden)> {
101 let mut instance_img = find_instance_img(pci_root)?;
102
103 let entry = locate_entry(&mut instance_img)?;
104 trace!("Found pvmfw instance.img entry: {entry:?}");
105
Alice Wang947f3f72023-09-29 09:04:07 +0000106 let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
Alice Wangee07f722023-10-03 15:20:17 +0000107 let tag_len = None;
108 let aead_ctx = AeadContext::new(Aead::aes_256_gcm_randnonce(), key.as_slice(), tag_len)?;
109 let ad = &[];
110 // The nonce is generated internally for `aes_256_gcm_randnonce`, so no additional
111 // nonce is required.
112 let nonce = &[];
113
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000114 let mut blk = [0; BLK_SIZE];
115 match entry {
116 PvmfwEntry::Existing { header_index, payload_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
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000124 let payload = &blk[..payload_size];
125 let mut entry = [0; size_of::<EntryBody>()];
Alice Wangee07f722023-10-03 15:20:17 +0000126 let decrypted = aead_ctx.open(payload, nonce, ad, &mut entry)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000127
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100128 let body = EntryBody::read_from(decrypted).unwrap();
Alice Wang12e4c862023-12-11 13:52:27 +0000129 if dice_inputs.rkp_vm_marker {
130 // The RKP VM is allowed to run if it has passed the verified boot check and
131 // contains the expected version in its AVB footer.
132 // The comparison below with the previous boot information is skipped to enable the
133 // simultaneous update of the pvmfw and RKP VM.
134 // For instance, when both the pvmfw and RKP VM are updated, the code hash of the
135 // RKP VM will differ from the one stored in the instance image. In this case, the
136 // RKP VM is still allowed to run.
137 // This ensures that the updated RKP VM will retain the same CDIs in the next stage.
138 return Ok((false, body.salt));
139 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000140 if body.code_hash != dice_inputs.code_hash {
141 Err(Error::RecordedCodeHashMismatch)
142 } else if body.auth_hash != dice_inputs.auth_hash {
143 Err(Error::RecordedAuthHashMismatch)
144 } else if body.mode() != dice_inputs.mode {
145 Err(Error::RecordedDiceModeMismatch)
146 } else {
147 Ok((false, body.salt))
148 }
149 }
150 PvmfwEntry::New { header_index } => {
Pierre-Clément Tosia59103d2023-02-02 14:46:55 +0000151 let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100152 let body = EntryBody::new(dice_inputs, &salt);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000153
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000154 // We currently only support single-blk entries.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100155 let plaintext = body.as_bytes();
Alice Wangee07f722023-10-03 15:20:17 +0000156 assert!(plaintext.len() + aead_ctx.aead().max_overhead() < blk.len());
157 let encrypted = aead_ctx.seal(plaintext, nonce, ad, &mut blk)?;
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +0000158 let payload_size = encrypted.len();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000159 let payload_index = header_index + 1;
160 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
161
162 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100163 header.write_to_prefix(blk.as_mut_slice()).unwrap();
164 blk[header.as_bytes().len()..].fill(0);
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000165 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
166
167 Ok((true, salt))
168 }
169 }
170}
171
Frederick Mayle2e779942023-10-15 18:27:31 +0000172#[derive(FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000173#[repr(C, packed)]
174struct Header {
175 magic: [u8; Header::MAGIC.len()],
176 version: u16,
177}
178
179impl Header {
Chris Wailes9d09f572024-01-16 13:31:02 -0800180 const MAGIC: &'static [u8] = b"Android-VM-instance";
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000181 const VERSION_1: u16 = 1;
182
183 pub fn is_valid(&self) -> bool {
184 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
185 }
186
187 fn version(&self) -> u16 {
188 u16::from_le(self.version)
189 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000190}
191
192fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000193 for transport in PciTransportIterator::<HalImpl>::new(pci_root)
194 .filter(|t| DeviceType::Block == t.device_type())
Alice Wang0e086232023-06-12 13:47:40 +0000195 {
Alice Wang7c55c7d2023-07-05 14:51:40 +0000196 let device =
197 VirtIOBlk::<HalImpl>::new(transport).map_err(Error::VirtIOBlkCreationFailed)?;
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000198 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)]
209enum PvmfwEntry {
210 Existing { header_index: usize, payload_size: usize },
211 New { header_index: usize },
212}
213
214const BLK_SIZE: usize = Partitions::LBA_SIZE;
215
216impl PvmfwEntry {
217 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
218}
219
220fn 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.
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100226 let header = Header::read_from_prefix(blk.as_slice()).unwrap();
227 if !header.is_valid() {
228 return Err(Error::InvalidInstanceImageHeader);
229 }
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000230
231 while let Some(header_index) = indices.next() {
232 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
233
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100234 let header = EntryHeader::read_from_prefix(blk.as_slice()).unwrap();
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000235 match (header.uuid(), header.payload_size()) {
236 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
237 (PvmfwEntry::UUID, payload_size) => {
238 return Ok(PvmfwEntry::Existing { header_index, payload_size })
239 }
240 (uuid, payload_size) => {
241 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
242 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
243 if n > 0 {
244 let _ = indices.nth(n - 1); // consume
245 }
246 }
247 };
248 }
249
250 Err(Error::InstanceImageFull)
251}
252
253/// Marks the start of an instance.img entry.
254///
255/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
Frederick Mayle2e779942023-10-15 18:27:31 +0000256#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi8ad980f2023-04-25 18:23:11 +0100257#[repr(C, packed)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000258struct EntryHeader {
259 uuid: u128,
260 payload_size: u64,
261}
262
263impl EntryHeader {
264 fn new(uuid: Uuid, payload_size: usize) -> Self {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100265 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 +0000266 }
267
268 fn uuid(&self) -> Uuid {
Pierre-Clément Tosiafb126a2023-03-29 14:42:19 +0100269 Uuid::from_u128_le(self.uuid)
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000270 }
271
272 fn payload_size(&self) -> usize {
273 usize::try_from(u64::from_le(self.payload_size)).unwrap()
274 }
275}
276
Frederick Mayle2e779942023-10-15 18:27:31 +0000277#[derive(AsBytes, FromZeroes, FromBytes)]
Pierre-Clément Tosi1cc5eb72023-02-02 11:09:18 +0000278#[repr(C)]
279struct EntryBody {
280 code_hash: Hash,
281 auth_hash: Hash,
282 salt: Hidden,
283 mode: u8,
284}
285
286impl EntryBody {
287 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
288 let mode = match dice_inputs.mode {
289 DiceMode::kDiceModeNotInitialized => 0,
290 DiceMode::kDiceModeNormal => 1,
291 DiceMode::kDiceModeDebug => 2,
292 DiceMode::kDiceModeMaintenance => 3,
293 };
294
295 Self {
296 code_hash: dice_inputs.code_hash,
297 auth_hash: dice_inputs.auth_hash,
298 salt: *salt,
299 mode,
300 }
301 }
302
303 fn mode(&self) -> DiceMode {
304 match self.mode {
305 1 => DiceMode::kDiceModeNormal,
306 2 => DiceMode::kDiceModeDebug,
307 3 => DiceMode::kDiceModeMaintenance,
308 _ => DiceMode::kDiceModeNotInitialized,
309 }
310 }
311}