lucet_module/
version_info.rs

1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
2use std::cmp::min;
3use std::fmt;
4use 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)]
11pub 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
27impl fmt::Display for VersionInfo {
28    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
41impl VersionInfo {
42    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.
54    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
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
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
102    pub fn valid(&self) -> bool {
103        self.reserved == 0x8000
104    }
105
106    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}