• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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