1 // Copyright 2019 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::fs::OpenOptions;
6 use std::io::prelude::*;
7 use std::mem;
8 use std::path::Path;
9 use std::path::PathBuf;
10 use std::result;
11 use std::slice;
12
13 use remain::sorted;
14 use thiserror::Error;
15 use vm_memory::GuestAddress;
16 use vm_memory::GuestMemory;
17 use zerocopy::AsBytes;
18 use zerocopy::FromBytes;
19
20 #[sorted]
21 #[derive(Error, Debug)]
22 pub enum Error {
23 /// The SMBIOS table has too little address space to be stored.
24 #[error("The SMBIOS table has too little address space to be stored")]
25 AddressOverflow,
26 /// Failure while zeroing out the memory for the SMBIOS table.
27 #[error("Failure while zeroing out the memory for the SMBIOS table")]
28 Clear,
29 /// Invalid table entry point checksum
30 #[error("Failure to verify host SMBIOS entry checksum")]
31 InvalidChecksum,
32 /// Incorrect or not readable host SMBIOS data
33 #[error("Failure to read host SMBIOS data")]
34 InvalidInput,
35 /// Failure while reading SMBIOS data file
36 #[error("Failure while reading SMBIOS data file")]
37 IoFailed,
38 /// There was too little guest memory to store the entire SMBIOS table.
39 #[error("There was too little guest memory to store the SMBIOS table")]
40 NotEnoughMemory,
41 /// A provided OEM string contained a null character
42 #[error("a provided SMBIOS OEM string contains a null character")]
43 OemStringHasNullCharacter,
44 /// Failure while opening SMBIOS data file
45 #[error("Failure while opening SMBIOS data file {1}: {0}")]
46 OpenFailed(std::io::Error, PathBuf),
47 /// Too many OEM strings provided
48 #[error("Too many OEM strings were provided, limited to 255")]
49 TooManyOemStrings,
50 /// Failure to write additional data to memory
51 #[error("Failure to write additional data to memory")]
52 WriteData,
53 /// Failure to write SMBIOS entrypoint structure
54 #[error("Failure to write SMBIOS entrypoint structure")]
55 WriteSmbiosEp,
56 }
57
58 pub type Result<T> = result::Result<T, Error>;
59
60 const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
61
62 // Constants sourced from SMBIOS Spec 2.3.1.
63 const SM2_MAGIC_IDENT: &[u8; 4usize] = b"_SM_";
64
65 // Constants sourced from SMBIOS Spec 3.2.0.
66 const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
67 const BIOS_INFORMATION: u8 = 0;
68 const SYSTEM_INFORMATION: u8 = 1;
69 const OEM_STRING: u8 = 11;
70 const END_OF_TABLE: u8 = 127;
71 const PCI_SUPPORTED: u64 = 1 << 7;
72 const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
73
compute_checksum<T: Copy>(v: &T) -> u874 fn compute_checksum<T: Copy>(v: &T) -> u8 {
75 // Safe because we are only reading the bytes within the size of the `T` reference `v`.
76 let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
77 let mut checksum: u8 = 0;
78 for i in v_slice.iter() {
79 checksum = checksum.wrapping_add(*i);
80 }
81 (!checksum).wrapping_add(1)
82 }
83
84 #[repr(C, packed)]
85 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
86 pub struct Smbios23Intermediate {
87 pub signature: [u8; 5usize],
88 pub checksum: u8,
89 pub length: u16,
90 pub address: u32,
91 pub count: u16,
92 pub revision: u8,
93 }
94
95 #[repr(C, packed)]
96 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
97 pub struct Smbios23Entrypoint {
98 pub signature: [u8; 4usize],
99 pub checksum: u8,
100 pub length: u8,
101 pub majorver: u8,
102 pub minorver: u8,
103 pub max_size: u16,
104 pub revision: u8,
105 pub reserved: [u8; 5usize],
106 pub dmi: Smbios23Intermediate,
107 }
108
109 #[repr(C, packed)]
110 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
111 pub struct Smbios30Entrypoint {
112 pub signature: [u8; 5usize],
113 pub checksum: u8,
114 pub length: u8,
115 pub majorver: u8,
116 pub minorver: u8,
117 pub docrev: u8,
118 pub revision: u8,
119 pub reserved: u8,
120 pub max_size: u32,
121 pub physptr: u64,
122 }
123
124 #[repr(C, packed)]
125 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
126 pub struct SmbiosBiosInfo {
127 pub typ: u8,
128 pub length: u8,
129 pub handle: u16,
130 pub vendor: u8,
131 pub version: u8,
132 pub start_addr: u16,
133 pub release_date: u8,
134 pub rom_size: u8,
135 pub characteristics: u64,
136 pub characteristics_ext1: u8,
137 pub characteristics_ext2: u8,
138 }
139
140 #[repr(C, packed)]
141 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
142 pub struct SmbiosSysInfo {
143 pub typ: u8,
144 pub length: u8,
145 pub handle: u16,
146 pub manufacturer: u8,
147 pub product_name: u8,
148 pub version: u8,
149 pub serial_number: u8,
150 pub uuid: [u8; 16usize],
151 pub wake_up_type: u8,
152 pub sku: u8,
153 pub family: u8,
154 }
155
156 #[repr(C, packed)]
157 #[derive(Default, Clone, Copy, FromBytes, AsBytes)]
158 pub struct SmbiosOemStrings {
159 pub typ: u8,
160 pub length: u8,
161 pub handle: u16,
162 pub count: u8,
163 }
164
write_and_incr<T: AsBytes + FromBytes>( mem: &GuestMemory, val: T, mut curptr: GuestAddress, ) -> Result<GuestAddress>165 fn write_and_incr<T: AsBytes + FromBytes>(
166 mem: &GuestMemory,
167 val: T,
168 mut curptr: GuestAddress,
169 ) -> Result<GuestAddress> {
170 mem.write_obj_at_addr(val, curptr)
171 .map_err(|_| Error::WriteData)?;
172 curptr = curptr
173 .checked_add(mem::size_of::<T>() as u64)
174 .ok_or(Error::NotEnoughMemory)?;
175 Ok(curptr)
176 }
177
write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress>178 fn write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress> {
179 for c in val.as_bytes().iter() {
180 curptr = write_and_incr(mem, *c, curptr)?;
181 }
182 curptr = write_and_incr(mem, 0_u8, curptr)?;
183 Ok(curptr)
184 }
185
setup_smbios_from_file(mem: &GuestMemory, path: &Path) -> Result<()>186 fn setup_smbios_from_file(mem: &GuestMemory, path: &Path) -> Result<()> {
187 let mut sme_path = PathBuf::from(path);
188 sme_path.push("smbios_entry_point");
189 let mut sme = Vec::new();
190 OpenOptions::new()
191 .read(true)
192 .open(&sme_path)
193 .map_err(|e| Error::OpenFailed(e, sme_path))?
194 .read_to_end(&mut sme)
195 .map_err(|_| Error::IoFailed)?;
196
197 let mut dmi_path = PathBuf::from(path);
198 dmi_path.push("DMI");
199 let mut dmi = Vec::new();
200 OpenOptions::new()
201 .read(true)
202 .open(&dmi_path)
203 .map_err(|e| Error::OpenFailed(e, dmi_path))?
204 .read_to_end(&mut dmi)
205 .map_err(|_| Error::IoFailed)?;
206
207 // Try SMBIOS 3.0 format.
208 if sme.len() == mem::size_of::<Smbios30Entrypoint>() && sme.starts_with(SM3_MAGIC_IDENT) {
209 let mut smbios_ep = Smbios30Entrypoint::default();
210 smbios_ep.as_bytes_mut().copy_from_slice(&sme);
211
212 let physptr = GuestAddress(SMBIOS_START)
213 .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
214 .ok_or(Error::NotEnoughMemory)?;
215
216 mem.write_at_addr(&dmi, physptr)
217 .map_err(|_| Error::NotEnoughMemory)?;
218
219 // Update EP DMI location
220 smbios_ep.physptr = physptr.offset();
221 smbios_ep.checksum = 0;
222 smbios_ep.checksum = compute_checksum(&smbios_ep);
223
224 mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
225 .map_err(|_| Error::NotEnoughMemory)?;
226
227 return Ok(());
228 }
229
230 // Try SMBIOS 2.3 format.
231 if sme.len() == mem::size_of::<Smbios23Entrypoint>() && sme.starts_with(SM2_MAGIC_IDENT) {
232 let mut smbios_ep = Smbios23Entrypoint::default();
233 smbios_ep.as_bytes_mut().copy_from_slice(&sme);
234
235 let physptr = GuestAddress(SMBIOS_START)
236 .checked_add(mem::size_of::<Smbios23Entrypoint>() as u64)
237 .ok_or(Error::NotEnoughMemory)?;
238
239 mem.write_at_addr(&dmi, physptr)
240 .map_err(|_| Error::NotEnoughMemory)?;
241
242 // Update EP DMI location
243 smbios_ep.dmi.address = physptr.offset() as u32;
244 smbios_ep.dmi.checksum = 0;
245 smbios_ep.dmi.checksum = compute_checksum(&smbios_ep.dmi);
246 smbios_ep.checksum = 0;
247 smbios_ep.checksum = compute_checksum(&smbios_ep);
248
249 mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
250 .map_err(|_| Error::WriteSmbiosEp)?;
251
252 return Ok(());
253 }
254
255 Err(Error::InvalidInput)
256 }
257
setup_smbios( mem: &GuestMemory, dmi_path: Option<PathBuf>, oem_strings: &[String], ) -> Result<()>258 pub fn setup_smbios(
259 mem: &GuestMemory,
260 dmi_path: Option<PathBuf>,
261 oem_strings: &[String],
262 ) -> Result<()> {
263 if let Some(dmi_path) = dmi_path {
264 return setup_smbios_from_file(mem, &dmi_path);
265 }
266
267 let physptr = GuestAddress(SMBIOS_START)
268 .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
269 .ok_or(Error::NotEnoughMemory)?;
270 let mut curptr = physptr;
271 let mut handle = 0;
272
273 {
274 handle += 1;
275 let smbios_biosinfo = SmbiosBiosInfo {
276 typ: BIOS_INFORMATION,
277 length: mem::size_of::<SmbiosBiosInfo>() as u8,
278 handle,
279 vendor: 1, // First string written in this section
280 version: 2, // Second string written in this section
281 characteristics: PCI_SUPPORTED,
282 characteristics_ext2: IS_VIRTUAL_MACHINE,
283 ..Default::default()
284 };
285 curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
286 curptr = write_string(mem, "crosvm", curptr)?;
287 curptr = write_string(mem, "0", curptr)?;
288 curptr = write_and_incr(mem, 0_u8, curptr)?;
289 }
290
291 {
292 handle += 1;
293 let smbios_sysinfo = SmbiosSysInfo {
294 typ: SYSTEM_INFORMATION,
295 length: mem::size_of::<SmbiosSysInfo>() as u8,
296 handle,
297 manufacturer: 1, // First string written in this section
298 product_name: 2, // Second string written in this section
299 ..Default::default()
300 };
301 curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
302 curptr = write_string(mem, "ChromiumOS", curptr)?;
303 curptr = write_string(mem, "crosvm", curptr)?;
304 curptr = write_and_incr(mem, 0u8, curptr)?;
305 }
306
307 if !oem_strings.is_empty() {
308 // AFAIK nothing prevents us from creating multiple OEM string tables
309 // if we have more than 255 strings, but 255 already seems pretty
310 // excessive.
311 if oem_strings.len() > u8::MAX.into() {
312 return Err(Error::TooManyOemStrings);
313 }
314 handle += 1;
315 let smbios_oemstring = SmbiosOemStrings {
316 typ: OEM_STRING,
317 length: mem::size_of::<SmbiosOemStrings>() as u8,
318 handle,
319 count: oem_strings.len() as u8,
320 };
321 curptr = write_and_incr(mem, smbios_oemstring, curptr)?;
322 for oem_string in oem_strings {
323 if oem_string.contains("\0") {
324 return Err(Error::OemStringHasNullCharacter);
325 }
326 curptr = write_string(mem, oem_string, curptr)?;
327 }
328 curptr = write_and_incr(mem, 0u8, curptr)?;
329 }
330
331 {
332 handle += 1;
333 let smbios_sysinfo = SmbiosSysInfo {
334 typ: END_OF_TABLE,
335 length: mem::size_of::<SmbiosSysInfo>() as u8,
336 handle,
337 ..Default::default()
338 };
339 curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
340 curptr = write_and_incr(mem, 0_u8, curptr)?;
341 }
342
343 {
344 let mut smbios_ep = Smbios30Entrypoint::default();
345 smbios_ep.signature = *SM3_MAGIC_IDENT;
346 smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
347 // SMBIOS rev 3.2.0
348 smbios_ep.majorver = 0x03;
349 smbios_ep.minorver = 0x02;
350 smbios_ep.docrev = 0x00;
351 smbios_ep.revision = 0x01; // SMBIOS 3.0
352 smbios_ep.max_size = curptr.offset_from(physptr) as u32;
353 smbios_ep.physptr = physptr.offset();
354 smbios_ep.checksum = compute_checksum(&smbios_ep);
355 mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
356 .map_err(|_| Error::WriteSmbiosEp)?;
357 }
358
359 Ok(())
360 }
361
362 #[cfg(test)]
363 mod tests {
364 use super::*;
365
366 #[test]
struct_size()367 fn struct_size() {
368 assert_eq!(
369 mem::size_of::<Smbios23Entrypoint>(),
370 0x1fusize,
371 concat!("Size of: ", stringify!(Smbios23Entrypoint))
372 );
373 assert_eq!(
374 mem::size_of::<Smbios30Entrypoint>(),
375 0x18usize,
376 concat!("Size of: ", stringify!(Smbios30Entrypoint))
377 );
378 assert_eq!(
379 mem::size_of::<SmbiosBiosInfo>(),
380 0x14usize,
381 concat!("Size of: ", stringify!(SmbiosBiosInfo))
382 );
383 assert_eq!(
384 mem::size_of::<SmbiosSysInfo>(),
385 0x1busize,
386 concat!("Size of: ", stringify!(SmbiosSysInfo))
387 );
388 assert_eq!(
389 mem::size_of::<SmbiosOemStrings>(),
390 0x5usize,
391 concat!("Size of: ", stringify!(SmbiosOemStrings))
392 )
393 }
394
395 #[test]
entrypoint_checksum()396 fn entrypoint_checksum() {
397 let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
398
399 // Use default 3.0 SMBIOS format.
400 setup_smbios(&mem, None, &Vec::new()).unwrap();
401
402 let smbios_ep: Smbios30Entrypoint =
403 mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();
404
405 assert_eq!(compute_checksum(&smbios_ep), 0);
406 }
407 }
408