|
| 1 | +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; |
| 2 | +use std::cmp::min; |
| 3 | +use std::fmt; |
| 4 | +use std::io; |
| 5 | + |
| 6 | +/// VersionInfo is information about a Lucet module to allow the Lucet runtime to determine if or |
| 7 | +/// how the module can be loaded, if so requested. The information here describes implementation |
| 8 | +/// details in runtime support for `lucetc`-produced modules, and nothing higher level. |
| 9 | +#[repr(C)] |
| 10 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 11 | +pub struct VersionInfo { |
| 12 | + major: u16, |
| 13 | + minor: u16, |
| 14 | + patch: u16, |
| 15 | + reserved: u16, |
| 16 | + version_hash: [u8; 8], |
| 17 | +} |
| 18 | + |
| 19 | +impl fmt::Display for VersionInfo { |
| 20 | + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
| 21 | + write!(fmt, "{}.{}.{}", self.major, self.minor, self.patch)?; |
| 22 | + if u64::from_ne_bytes(self.version_hash) != 0 { |
| 23 | + write!( |
| 24 | + fmt, |
| 25 | + "-{}", |
| 26 | + std::str::from_utf8(&self.version_hash).unwrap_or("INVALID") |
| 27 | + )?; |
| 28 | + } |
| 29 | + Ok(()) |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +impl VersionInfo { |
| 34 | + pub fn write_to<W: WriteBytesExt>(&self, w: &mut W) -> io::Result<()> { |
| 35 | + w.write_u16::<LittleEndian>(self.major)?; |
| 36 | + w.write_u16::<LittleEndian>(self.minor)?; |
| 37 | + w.write_u16::<LittleEndian>(self.patch)?; |
| 38 | + w.write_u16::<LittleEndian>(self.reserved)?; |
| 39 | + w.write(&self.version_hash).and_then(|written| { |
| 40 | + if written != self.version_hash.len() { |
| 41 | + Err(io::Error::new( |
| 42 | + io::ErrorKind::Other, |
| 43 | + "unable to write full version hash", |
| 44 | + )) |
| 45 | + } else { |
| 46 | + Ok(()) |
| 47 | + } |
| 48 | + }) |
| 49 | + } |
| 50 | + |
| 51 | + pub fn read_from<R: ReadBytesExt>(r: &mut R) -> io::Result<Self> { |
| 52 | + let mut version_hash = [0u8; 8]; |
| 53 | + Ok(VersionInfo { |
| 54 | + major: r.read_u16::<LittleEndian>()?, |
| 55 | + minor: r.read_u16::<LittleEndian>()?, |
| 56 | + patch: r.read_u16::<LittleEndian>()?, |
| 57 | + reserved: r.read_u16::<LittleEndian>()?, |
| 58 | + version_hash: { |
| 59 | + r.read_exact(&mut version_hash)?; |
| 60 | + version_hash |
| 61 | + }, |
| 62 | + }) |
| 63 | + } |
| 64 | + |
| 65 | + pub fn valid(&self) -> bool { |
| 66 | + self.reserved == 0x8000 |
| 67 | + } |
| 68 | + |
| 69 | + pub fn current(current_hash: &'static [u8]) -> Self { |
| 70 | + let mut version_hash = [0u8; 8]; |
| 71 | + |
| 72 | + for i in 0..min(version_hash.len(), current_hash.len()) { |
| 73 | + version_hash[i] = current_hash[i]; |
| 74 | + } |
| 75 | + |
| 76 | + // The reasoning for this is as follows: |
| 77 | + // `SerializedModule`, in version before version information was introduced, began with a |
| 78 | + // pointer - `module_data_ptr`. This pointer would be relocated to somewhere in user space |
| 79 | + // for the embedder of `lucet-runtime`. On x86_64, hopefully, that's userland code in some |
| 80 | + // OS, meaning the pointer will be a pointer to user memory, and will be below |
| 81 | + // 0x8000_0000_0000_0000. By setting `reserved` to `0x8000`, we set what would be the |
| 82 | + // highest bit in `module_data_ptr` in an old `lucet-runtime` and guarantee a segmentation |
| 83 | + // fault when loading these newer modules with version information. |
| 84 | + VersionInfo { |
| 85 | + major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), |
| 86 | + minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), |
| 87 | + patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), |
| 88 | + reserved: 0x8000u16, |
| 89 | + version_hash, |
| 90 | + } |
| 91 | + } |
| 92 | +} |
0 commit comments