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

Commit 9128751

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).
1 parent f495487 commit 9128751

File tree

12 files changed

+179
-26
lines changed

12 files changed

+179
-26
lines changed

lucet-analyze/src/main.rs

Lines changed: 7 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,12 @@ impl<'a> ArtifactSummary<'a> {
102103
.unwrap();
103104
let mut rdr = Cursor::new(buffer);
104105

106+
let version = unsafe {
107+
std::mem::transmute::<u64, VersionInfo>(rdr.read_u64::<LittleEndian>().unwrap())
108+
};
109+
105110
SerializedModule {
111+
version,
106112
module_data_ptr: rdr.read_u64::<LittleEndian>().unwrap(),
107113
module_data_len: rdr.read_u64::<LittleEndian>().unwrap(),
108114
tables_ptr: rdr.read_u64::<LittleEndian>().unwrap(),
@@ -211,6 +217,7 @@ fn load_module<'b, 'a: 'b>(
211217
)
212218
};
213219
Module {
220+
version: serialized_module.version.clone(),
214221
module_data,
215222
tables,
216223
function_manifest,

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/version_info.rs

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

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
use std::env;
2+
use std::fs::File;
3+
use std::io::Write;
4+
use std::path::Path;
5+
use std::process::Command;
6+
17
use cc;
28

39
fn main() {
@@ -14,4 +20,16 @@ fn main() {
1420
cc::Build::new()
1521
.file("src/context/tests/c_child.c")
1622
.compile("context_tests_c_child");
23+
24+
let last_commit_hash = Command::new("git")
25+
.args(&["log", "-n", "1", "--pretty=format:%H"])
26+
.output()
27+
.ok();
28+
29+
let commit_file_path = Path::new(&env::var("OUT_DIR").unwrap()).join("commit_hash");
30+
let mut f = File::create(&commit_file_path).unwrap();
31+
32+
if let Some(last_commit_hash) = last_commit_hash {
33+
f.write_all(&last_commit_hash.stdout).unwrap();
34+
}
1735
}

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_module2.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+
}
9.2 KB
Binary file not shown.

lucet-spectest/src/script.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::bindings;
22
use failure::{format_err, Error, Fail};
3+
use lucet_module::CapabilitiesBuilder;
34
use lucet_runtime::{self, MmapRegion, Module as LucetModule, Region, UntypedRetVal, Val};
45
use lucetc::{Compiler, HeapSettings, LucetcError, LucetcErrorKind, OptLevel};
56
use std::io;
@@ -72,7 +73,9 @@ impl ScriptEnv {
7273
OptLevel::Fast,
7374
&bindings,
7475
HeapSettings::default(),
75-
true,
76+
CapabilitiesBuilder::new()
77+
.with_instructions_counted(true)
78+
.build(),
7679
)
7780
.map_err(program_error)?;
7881

lucet-wasi-sdk/tests/lucetc.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod lucetc_tests {
33
use failure::Error;
44
use lucet_module::bindings::Bindings;
5+
use lucet_module::Capabilities;
56
use lucet_wasi_sdk::*;
67
use lucetc::{Compiler, HeapSettings, OptLevel};
78
use std::collections::HashMap;
@@ -39,7 +40,8 @@ mod lucetc_tests {
3940
let m = module_from_c(&["empty"], &[]).expect("build module for empty");
4041
let b = Bindings::empty();
4142
let h = HeapSettings::default();
42-
let c = Compiler::new(&m, OptLevel::Fast, &b, h, false).expect("compile empty");
43+
let c = Compiler::new(&m, OptLevel::Fast, &b, h, Capabilities::default())
44+
.expect("compile empty");
4345
let mdata = c.module_data().unwrap();
4446
assert!(mdata.heap_spec().is_some());
4547
// clang creates 3 globals:
@@ -74,7 +76,8 @@ mod lucetc_tests {
7476
let m = module_from_c(&["c"], &["c"]).expect("build module for c");
7577
let b = Bindings::empty();
7678
let h = HeapSettings::default();
77-
let c = Compiler::new(&m, OptLevel::Fast, &b, h, false).expect("compile c");
79+
let c =
80+
Compiler::new(&m, OptLevel::Fast, &b, h, Capabilities::default()).expect("compile c");
7881
let mdata = c.module_data().unwrap();
7982
assert_eq!(mdata.import_functions().len(), 0, "import functions");
8083
assert_eq!(mdata.export_functions().len(), 1, "export functions");
@@ -91,7 +94,8 @@ mod lucetc_tests {
9194
let m = module_from_c(&["d"], &["d"]).expect("build module for d");
9295
let b = d_only_test_bindings();
9396
let h = HeapSettings::default();
94-
let c = Compiler::new(&m, OptLevel::Fast, &b, h, false).expect("compile d");
97+
let c =
98+
Compiler::new(&m, OptLevel::Fast, &b, h, Capabilities::default()).expect("compile d");
9599
let mdata = c.module_data().unwrap();
96100
assert_eq!(mdata.import_functions().len(), 1, "import functions");
97101
assert_eq!(mdata.export_functions().len(), 1, "export functions");
@@ -107,7 +111,8 @@ mod lucetc_tests {
107111
let m = module_from_c(&["c", "d"], &["c", "d"]).expect("build module for c & d");
108112
let b = Bindings::empty();
109113
let h = HeapSettings::default();
110-
let c = Compiler::new(&m, OptLevel::Fast, &b, h, false).expect("compile c & d");
114+
let c = Compiler::new(&m, OptLevel::Fast, &b, h, Capabilities::default())
115+
.expect("compile c & d");
111116
let mdata = c.module_data().unwrap();
112117
assert_eq!(mdata.import_functions().len(), 0, "import functions");
113118
assert_eq!(mdata.export_functions().len(), 2, "export functions");

0 commit comments

Comments
 (0)