blob: 3657fb6df507e7acc98ee370e421225142453660 [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;
21use crate::helpers::ceiling_div;
22use crate::virtio::pci::VirtIOBlkIterator;
23use core::fmt;
24use core::mem::size_of;
25use core::slice;
26use diced_open_dice::DiceMode;
27use diced_open_dice::Hash;
28use diced_open_dice::Hidden;
29use log::trace;
30use uuid::Uuid;
31use virtio_drivers::transport::pci::bus::PciRoot;
32
33pub enum Error {
34 /// Unexpected I/O error while accessing the underlying disk.
35 FailedIo(gpt::Error),
36 /// Impossible to create a new instance.img entry.
37 InstanceImageFull,
38 /// Badly formatted instance.img header block.
39 InvalidInstanceImageHeader,
40 /// No instance.img ("vm-instance") partition found.
41 MissingInstanceImage,
42 /// The instance.img doesn't contain a header.
43 MissingInstanceImageHeader,
44 /// Authority hash found in the pvmfw instance.img entry doesn't match the trusted public key.
45 RecordedAuthHashMismatch,
46 /// Code hash found in the pvmfw instance.img entry doesn't match the inputs.
47 RecordedCodeHashMismatch,
48 /// DICE mode found in the pvmfw instance.img entry doesn't match the current one.
49 RecordedDiceModeMismatch,
50 /// Size of the instance.img entry being read or written is not supported.
51 UnsupportedEntrySize(usize),
52}
53
54impl fmt::Display for Error {
55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56 match self {
57 Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
58 Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
59 Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
60 Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
61 Self::MissingInstanceImageHeader => write!(f, "instance.img header is missing"),
62 Self::RecordedAuthHashMismatch => write!(f, "Recorded authority hash doesn't match"),
63 Self::RecordedCodeHashMismatch => write!(f, "Recorded code hash doesn't match"),
64 Self::RecordedDiceModeMismatch => write!(f, "Recorded DICE mode doesn't match"),
65 Self::UnsupportedEntrySize(sz) => write!(f, "Invalid entry size: {sz}"),
66 }
67 }
68}
69
70pub type Result<T> = core::result::Result<T, Error>;
71
72pub fn get_or_generate_instance_salt(
73 pci_root: &mut PciRoot,
74 dice_inputs: &PartialInputs,
75) -> Result<(bool, Hidden)> {
76 let mut instance_img = find_instance_img(pci_root)?;
77
78 let entry = locate_entry(&mut instance_img)?;
79 trace!("Found pvmfw instance.img entry: {entry:?}");
80
81 let mut blk = [0; BLK_SIZE];
82 match entry {
83 PvmfwEntry::Existing { header_index, payload_size } => {
84 if payload_size > blk.len() {
85 // We currently only support single-blk entries.
86 return Err(Error::UnsupportedEntrySize(payload_size));
87 }
88 let payload_index = header_index + 1;
89 instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
90
91 let payload = &blk[..payload_size]; // TODO(b/249723852): Decrypt entries.
92
93 let body: &EntryBody = payload.as_ref();
94 if body.code_hash != dice_inputs.code_hash {
95 Err(Error::RecordedCodeHashMismatch)
96 } else if body.auth_hash != dice_inputs.auth_hash {
97 Err(Error::RecordedAuthHashMismatch)
98 } else if body.mode() != dice_inputs.mode {
99 Err(Error::RecordedDiceModeMismatch)
100 } else {
101 Ok((false, body.salt))
102 }
103 }
104 PvmfwEntry::New { header_index } => {
105 let salt = [0; size_of::<Hidden>()]; // TODO(b/262393451): Generate using TRNG.
106 let entry_body = EntryBody::new(dice_inputs, &salt);
107 let body = entry_body.as_ref();
108 // We currently only support single-blk entries.
109 assert!(body.len() < blk.len());
110
111 let payload_size = body.len();
112 blk[..payload_size].copy_from_slice(body); // TODO(b/249723852): Encrypt entries.
113 let payload_index = header_index + 1;
114 instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
115
116 let header = EntryHeader::new(PvmfwEntry::UUID, payload_size);
117 let (blk_header, blk_rest) = blk.split_at_mut(size_of::<EntryHeader>());
118 blk_header.copy_from_slice(header.as_ref());
119 blk_rest.fill(0);
120 instance_img.write_block(header_index, &blk).map_err(Error::FailedIo)?;
121
122 Ok((true, salt))
123 }
124 }
125}
126
127#[repr(C, packed)]
128struct Header {
129 magic: [u8; Header::MAGIC.len()],
130 version: u16,
131}
132
133impl Header {
134 const MAGIC: &[u8] = b"Android-VM-instance";
135 const VERSION_1: u16 = 1;
136
137 pub fn is_valid(&self) -> bool {
138 self.magic == Self::MAGIC && self.version() == Self::VERSION_1
139 }
140
141 fn version(&self) -> u16 {
142 u16::from_le(self.version)
143 }
144
145 fn from_bytes(bytes: &[u8]) -> Option<&Self> {
146 let header: &Self = bytes.as_ref();
147
148 if header.is_valid() {
149 Some(header)
150 } else {
151 None
152 }
153 }
154}
155
156impl AsRef<Header> for [u8] {
157 fn as_ref(&self) -> &Header {
158 // SAFETY - Assume that the alignement and size match Header.
159 unsafe { &*self.as_ptr().cast::<Header>() }
160 }
161}
162
163fn find_instance_img(pci_root: &mut PciRoot) -> Result<Partition> {
164 for device in VirtIOBlkIterator::new(pci_root) {
165 match Partition::get_by_name(device, "vm-instance") {
166 Ok(Some(p)) => return Ok(p),
167 Ok(None) => {}
168 Err(e) => log::warn!("error while reading from disk: {e}"),
169 };
170 }
171
172 Err(Error::MissingInstanceImage)
173}
174
175#[derive(Debug)]
176enum PvmfwEntry {
177 Existing { header_index: usize, payload_size: usize },
178 New { header_index: usize },
179}
180
181const BLK_SIZE: usize = Partitions::LBA_SIZE;
182
183impl PvmfwEntry {
184 const UUID: Uuid = Uuid::from_u128(0x90d2174a038a4bc6adf3824848fc5825);
185}
186
187fn locate_entry(partition: &mut Partition) -> Result<PvmfwEntry> {
188 let mut blk = [0; BLK_SIZE];
189 let mut indices = partition.indices();
190 let header_index = indices.next().ok_or(Error::MissingInstanceImageHeader)?;
191 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
192 // The instance.img header is only used for discovery/validation.
193 let _ = Header::from_bytes(&blk).ok_or(Error::InvalidInstanceImageHeader)?;
194
195 while let Some(header_index) = indices.next() {
196 partition.read_block(header_index, &mut blk).map_err(Error::FailedIo)?;
197
198 let header: &EntryHeader = blk[..size_of::<EntryHeader>()].as_ref();
199 match (header.uuid(), header.payload_size()) {
200 (uuid, _) if uuid.is_nil() => return Ok(PvmfwEntry::New { header_index }),
201 (PvmfwEntry::UUID, payload_size) => {
202 return Ok(PvmfwEntry::Existing { header_index, payload_size })
203 }
204 (uuid, payload_size) => {
205 trace!("Skipping instance.img entry {uuid}: {payload_size:?} bytes");
206 let n = ceiling_div(payload_size, BLK_SIZE).unwrap();
207 if n > 0 {
208 let _ = indices.nth(n - 1); // consume
209 }
210 }
211 };
212 }
213
214 Err(Error::InstanceImageFull)
215}
216
217/// Marks the start of an instance.img entry.
218///
219/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
220#[repr(C)]
221struct EntryHeader {
222 uuid: u128,
223 payload_size: u64,
224}
225
226impl EntryHeader {
227 fn new(uuid: Uuid, payload_size: usize) -> Self {
228 Self { uuid: uuid.as_u128(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
229 }
230
231 fn uuid(&self) -> Uuid {
232 Uuid::from_u128(self.uuid)
233 }
234
235 fn payload_size(&self) -> usize {
236 usize::try_from(u64::from_le(self.payload_size)).unwrap()
237 }
238}
239
240impl AsRef<EntryHeader> for [u8] {
241 fn as_ref(&self) -> &EntryHeader {
242 assert_eq!(self.len(), size_of::<EntryHeader>());
243 // SAFETY - The size of the slice was checked and any value may be considered valid.
244 unsafe { &*self.as_ptr().cast::<EntryHeader>() }
245 }
246}
247
248impl AsRef<[u8]> for EntryHeader {
249 fn as_ref(&self) -> &[u8] {
250 let s = self as *const Self;
251 // SAFETY - Transmute the (valid) bytes into a slice.
252 unsafe { slice::from_raw_parts(s.cast::<u8>(), size_of::<Self>()) }
253 }
254}
255
256#[repr(C)]
257struct EntryBody {
258 code_hash: Hash,
259 auth_hash: Hash,
260 salt: Hidden,
261 mode: u8,
262}
263
264impl EntryBody {
265 fn new(dice_inputs: &PartialInputs, salt: &Hidden) -> Self {
266 let mode = match dice_inputs.mode {
267 DiceMode::kDiceModeNotInitialized => 0,
268 DiceMode::kDiceModeNormal => 1,
269 DiceMode::kDiceModeDebug => 2,
270 DiceMode::kDiceModeMaintenance => 3,
271 };
272
273 Self {
274 code_hash: dice_inputs.code_hash,
275 auth_hash: dice_inputs.auth_hash,
276 salt: *salt,
277 mode,
278 }
279 }
280
281 fn mode(&self) -> DiceMode {
282 match self.mode {
283 1 => DiceMode::kDiceModeNormal,
284 2 => DiceMode::kDiceModeDebug,
285 3 => DiceMode::kDiceModeMaintenance,
286 _ => DiceMode::kDiceModeNotInitialized,
287 }
288 }
289}
290
291impl AsRef<EntryBody> for [u8] {
292 fn as_ref(&self) -> &EntryBody {
293 assert_eq!(self.len(), size_of::<EntryBody>());
294 // SAFETY - The size of the slice was checked and members are validated by accessors.
295 unsafe { &*self.as_ptr().cast::<EntryBody>() }
296 }
297}
298
299impl AsRef<[u8]> for EntryBody {
300 fn as_ref(&self) -> &[u8] {
301 let s = self as *const Self;
302 // SAFETY - Transmute the (valid) bytes into a slice.
303 unsafe { slice::from_raw_parts(s.cast::<u8>(), size_of::<Self>()) }
304 }
305}