Skip to content
This repository was archived by the owner on Mar 24, 2022. It is now read-only.

Commit ff40f4d

Browse files
add version info to modules
version information is comprised of both the current crate version and the current git commit hash (if available). the current git commit hash is only used in release builds to avoid too much furstration in typical development workflows using tools like "git commit --amend" or "git rebase", or just making non-conflicting spot changes to only one of lucetc or lucet-runtime
1 parent f495487 commit ff40f4d

File tree

13 files changed

+205
-6
lines changed

13 files changed

+205
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lucet-analyze/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use lucet_module::{
44
FunctionSpec, Module, ModuleData, SerializedModule, TableElement, TrapManifest, TrapSite,
5+
VersionInfo,
56
};
67

78
use byteorder::{LittleEndian, ReadBytesExt};
@@ -102,7 +103,10 @@ impl<'a> ArtifactSummary<'a> {
102103
.unwrap();
103104
let mut rdr = Cursor::new(buffer);
104105

106+
let version = VersionInfo::read_from(&mut rdr).unwrap();
107+
105108
SerializedModule {
109+
version,
106110
module_data_ptr: rdr.read_u64::<LittleEndian>().unwrap(),
107111
module_data_len: rdr.read_u64::<LittleEndian>().unwrap(),
108112
tables_ptr: rdr.read_u64::<LittleEndian>().unwrap(),
@@ -211,6 +215,7 @@ fn load_module<'b, 'a: 'b>(
211215
)
212216
};
213217
Module {
218+
version: serialized_module.version.clone(),
214219
module_data,
215220
tables,
216221
function_manifest,

lucet-module/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ num-traits = "0.2"
2020
minisign = "0.5.11"
2121
object = "0.12"
2222
byteorder = "1.3"
23+
memoffset = "0.5.1"

lucet-module/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod signature;
1717
mod tables;
1818
mod traps;
1919
mod types;
20+
mod version_info;
2021

2122
pub use crate::error::Error;
2223
pub use crate::functions::{
@@ -32,6 +33,7 @@ pub use crate::signature::{ModuleSignature, PublicKey};
3233
pub use crate::tables::TableElement;
3334
pub use crate::traps::{TrapCode, TrapManifest, TrapSite};
3435
pub use crate::types::{Signature, ValueType};
36+
pub use crate::version_info::VersionInfo;
3537

3638
/// Owned variants of the module data types, useful for serialization and testing.
3739
pub mod owned {

lucet-module/src/module.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::functions::FunctionSpec;
22
use crate::module_data::ModuleData;
33
use crate::tables::TableElement;
4+
use crate::version_info::VersionInfo;
45

56
pub const LUCET_MODULE_SYM: &str = "lucet_module";
67

78
/// Module is the exposed structure that contains all the data backing a Lucet-compiled object.
89
#[derive(Debug)]
910
pub struct Module<'a> {
11+
pub version: VersionInfo,
1012
pub module_data: ModuleData<'a>,
1113
pub tables: &'a [&'a [TableElement]],
1214
pub function_manifest: &'a [FunctionSpec],
@@ -18,6 +20,7 @@ pub struct Module<'a> {
1820
#[repr(C)]
1921
#[derive(Debug)]
2022
pub struct SerializedModule {
23+
pub version: VersionInfo,
2124
pub module_data_ptr: u64,
2225
pub module_data_len: u64,
2326
pub tables_ptr: u64,

lucet-module/src/signature.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::error::Error::{self, IOError, ModuleSignatureError};
2-
use crate::module::LUCET_MODULE_SYM;
2+
use memoffset::offset_of;
3+
use crate::module::{SerializedModule, LUCET_MODULE_SYM};
34
use crate::module_data::MODULE_DATA_SYM;
45
use crate::ModuleData;
56
use byteorder::{ByteOrder, LittleEndian};
@@ -95,11 +96,11 @@ impl RawModuleAndData {
9596
))?;
9697

9798
let module_data_len =
98-
LittleEndian::read_u64(&obj_bin[(native_data_symbol_data.offset + 8)..]) as usize;
99+
LittleEndian::read_u64(&obj_bin[(native_data_symbol_data.offset + offset_of!(SerializedModule, module_data_len))..]) as usize;
99100

100101
Ok(RawModuleAndData {
101102
obj_bin,
102-
module_data_offset: module_data_symbol_data.offset,
103+
module_data_offset: module_data_symbol_data.offset + offset_of!(SerializedModule, module_data_ptr),
103104
module_data_len: module_data_len,
104105
})
105106
}

lucet-module/src/version_info.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
}

lucet-runtime/lucet-runtime-internals/build.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use std::env;
2+
use std::fs::File;
3+
use std::path::Path;
4+
15
use cc;
26

37
fn main() {
@@ -14,4 +18,28 @@ fn main() {
1418
cc::Build::new()
1519
.file("src/context/tests/c_child.c")
1620
.compile("context_tests_c_child");
21+
22+
let commit_file_path = Path::new(&env::var("OUT_DIR").unwrap()).join("commit_hash");
23+
// in debug builds we only need the file to exist, but in release builds this will be used and
24+
// requires mutability.
25+
#[allow(unused_variables, unused_mut)]
26+
let mut f = File::create(&commit_file_path).unwrap();
27+
28+
// This is about the closest not-additional-feature-flag way to detect release builds.
29+
// In debug builds, leave the `commit_hash` file empty to allow looser version checking and
30+
// avoid impacting development workflows too much.
31+
#[cfg(not(debug_assertions))]
32+
{
33+
use std::io::Write;
34+
use std::process::Command;
35+
36+
let last_commit_hash = Command::new("git")
37+
.args(&["log", "-n", "1", "--pretty=format:%H"])
38+
.output()
39+
.ok();
40+
41+
if let Some(last_commit_hash) = last_commit_hash {
42+
f.write_all(&last_commit_hash.stdout).unwrap();
43+
}
44+
}
1745
}

lucet-runtime/lucet-runtime-internals/src/module/dl.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use libc::c_void;
44
use libloading::Library;
55
use lucet_module::{
66
FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleSignature,
7-
PublicKey, SerializedModule, Signature, LUCET_MODULE_SYM,
7+
PublicKey, SerializedModule, Signature, VersionInfo, LUCET_MODULE_SYM,
88
};
99
use std::ffi::CStr;
1010
use std::mem::MaybeUninit;
@@ -61,6 +61,21 @@ impl DlModule {
6161
let serialized_module: &SerializedModule =
6262
unsafe { serialized_module_ptr.as_ref().unwrap() };
6363

64+
let version = serialized_module.version.clone();
65+
66+
let runtime_version =
67+
VersionInfo::current(include_str!(concat!(env!("OUT_DIR"), "/commit_hash")).as_bytes());
68+
69+
if !version.valid() {
70+
return Err(lucet_incorrect_module!("reserved bit is not set. This module is likely too old for this lucet-runtime to load."));
71+
} else if version != runtime_version {
72+
return Err(lucet_incorrect_module!(
73+
"version mismatch. module has version {:?}, while this runtime is version {:?}",
74+
version,
75+
runtime_version,
76+
));
77+
}
78+
6479
// Deserialize the slice into ModuleData, which will hold refs into the loaded
6580
// shared object file in `module_data_slice`. Both of these get a 'static lifetime because
6681
// Rust doesn't have a safe way to describe that their lifetime matches the containing
@@ -115,6 +130,7 @@ impl DlModule {
115130
lib,
116131
fbase,
117132
module: lucet_module::Module {
133+
version,
118134
module_data,
119135
tables,
120136
function_manifest,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use lucet_runtime::{DlModule, Error};
2+
3+
#[test]
4+
pub fn reject_old_modules() {
5+
let err = DlModule::load("./tests/version_checks/old_module.so")
6+
.err()
7+
.unwrap();
8+
9+
if let Error::ModuleError(e) = err {
10+
let msg = format!("{}", e);
11+
assert!(msg.contains("reserved bit is not set"));
12+
assert!(msg.contains("module is likely too old"));
13+
} else {
14+
panic!("unexpected error loading module: {}", err);
15+
}
16+
}

0 commit comments

Comments
 (0)