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` is either all nulls or the first eight ascii characters of the git commit 17 /// hash of wherever this Version is coming from. In the case of a compiled lucet module, this 18 /// hash will come from the git commit that the lucetc producing it came from. In a runtime 19 /// context, it will be the git commit of lucet-runtime built into the embedder. 20 /// 21 /// The version hash will typically populated only in release builds, but may blank even in 22 /// that case: if building from a packaged crate, or in a build environment that does not have 23 /// "git" installed, `lucetc` and `lucet-runtime` will fall back to an empty hash. 24 version_hash: [u8; 8], 25 } 26 27 impl fmt::Display for VersionInfo { fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result28 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 29 write!(fmt, "{}.{}.{}", self.major, self.minor, self.patch)?; 30 if u64::from_ne_bytes(self.version_hash) != 0 { 31 write!( 32 fmt, 33 "-{}", 34 std::str::from_utf8(&self.version_hash).unwrap_or("INVALID") 35 )?; 36 } 37 Ok(()) 38 } 39 } 40 41 impl VersionInfo { new(major: u16, minor: u16, patch: u16, version_hash: [u8; 8]) -> VersionInfo42 pub fn new(major: u16, minor: u16, patch: u16, version_hash: [u8; 8]) -> VersionInfo { 43 VersionInfo { 44 major, 45 minor, 46 patch, 47 reserved: 0x8000, 48 version_hash, 49 } 50 } 51 52 /// A more permissive version check than for version equality. This check will allow an `other` 53 /// version that is more specific than `self`, but matches for data that is available. compatible_with(&self, other: &VersionInfo) -> bool54 pub fn compatible_with(&self, other: &VersionInfo) -> bool { 55 if !(self.valid() || other.valid()) { 56 return false; 57 } 58 59 if self.major == other.major && self.minor == other.minor && self.patch == other.patch { 60 if self.version_hash == [0u8; 8] { 61 // we aren't bound to a specific git commit, so anything goes. 62 true 63 } else { 64 self.version_hash == other.version_hash 65 } 66 } else { 67 false 68 } 69 } 70 write_to<W: WriteBytesExt>(&self, w: &mut W) -> io::Result<()>71 pub fn write_to<W: WriteBytesExt>(&self, w: &mut W) -> io::Result<()> { 72 w.write_u16::<LittleEndian>(self.major)?; 73 w.write_u16::<LittleEndian>(self.minor)?; 74 w.write_u16::<LittleEndian>(self.patch)?; 75 w.write_u16::<LittleEndian>(self.reserved)?; 76 w.write(&self.version_hash).and_then(|written| { 77 if written != self.version_hash.len() { 78 Err(io::Error::new( 79 io::ErrorKind::Other, 80 "unable to write full version hash", 81 )) 82 } else { 83 Ok(()) 84 } 85 }) 86 } 87 read_from<R: ReadBytesExt>(r: &mut R) -> io::Result<Self>88 pub fn read_from<R: ReadBytesExt>(r: &mut R) -> io::Result<Self> { 89 let mut version_hash = [0u8; 8]; 90 Ok(VersionInfo { 91 major: r.read_u16::<LittleEndian>()?, 92 minor: r.read_u16::<LittleEndian>()?, 93 patch: r.read_u16::<LittleEndian>()?, 94 reserved: r.read_u16::<LittleEndian>()?, 95 version_hash: { 96 r.read_exact(&mut version_hash)?; 97 version_hash 98 }, 99 }) 100 } 101 valid(&self) -> bool102 pub fn valid(&self) -> bool { 103 self.reserved == 0x8000 104 } 105 current(current_hash: &'static [u8]) -> Self106 pub fn current(current_hash: &'static [u8]) -> Self { 107 let mut version_hash = [0u8; 8]; 108 109 for i in 0..min(version_hash.len(), current_hash.len()) { 110 version_hash[i] = current_hash[i]; 111 } 112 113 // The reasoning for this is as follows: 114 // `SerializedModule`, in version before version information was introduced, began with a 115 // pointer - `module_data_ptr`. This pointer would be relocated to somewhere in user space 116 // for the embedder of `lucet-runtime`. On x86_64, hopefully, that's userland code in some 117 // OS, meaning the pointer will be a pointer to user memory, and will be below 118 // 0x8000_0000_0000_0000. By setting `reserved` to `0x8000`, we set what would be the 119 // highest bit in `module_data_ptr` in an old `lucet-runtime` and guarantee a segmentation 120 // fault when loading these newer modules with version information. 121 VersionInfo::new( 122 env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), 123 env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), 124 env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), 125 version_hash, 126 ) 127 } 128 } 129