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

Partially address #282 - allow statically linked wasm modules #301

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lucet-module/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ serde_json = "1.0"
bincode = "1.1.4"
num-derive = "0.2"
num-traits = "0.2"
minisign = "0.5.11"
minisign = { version = "0.5.11", optional = true }
object = "0.12"
byteorder = "1.3"

[features]
default = ["signature_checking"]
signature_checking = ["minisign"]
1 change: 1 addition & 0 deletions lucet-module/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum Error {
DeserializationError(#[cause] bincode::Error),
#[fail(display = "Serialization error: {}", _0)]
SerializationError(#[cause] bincode::Error),
#[cfg(feature = "signature_checking")]
#[fail(display = "Module signature error: {}", _0)]
ModuleSignatureError(#[cause] minisign::PError),
#[fail(display = "I/O error: {}", _0)]
Expand Down
4 changes: 3 additions & 1 deletion lucet-module/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ pub use crate::linear_memory::{HeapSpec, LinearMemorySpec, SparseData};
pub use crate::module::{Module, SerializedModule, LUCET_MODULE_SYM};
pub use crate::module_data::{ModuleData, MODULE_DATA_SYM};
pub use crate::runtime::InstanceRuntimeData;
pub use crate::signature::{ModuleSignature, PublicKey};
pub use crate::signature::ModuleSignature;
#[cfg(feature = "signature_checking")]
pub use crate::signature::PublicKey;
pub use crate::tables::TableElement;
pub use crate::traps::{TrapCode, TrapManifest, TrapSite};
pub use crate::types::{Signature, ValueType};
Expand Down
25 changes: 25 additions & 0 deletions lucet-module/src/module_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
types::Signature,
Error,
};
#[cfg(feature = "signature_checking")]
use minisign::SignatureBones;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -37,6 +38,7 @@ pub struct ModuleData<'a> {
}

impl<'a> ModuleData<'a> {
#[cfg(feature = "signature_checking")]
pub fn new(
linear_memory: Option<LinearMemorySpec<'a>>,
globals_spec: Vec<GlobalSpec<'a>>,
Expand All @@ -57,6 +59,27 @@ impl<'a> ModuleData<'a> {
}
}

#[cfg(not(feature = "signature_checking"))]
pub fn new(
linear_memory: Option<LinearMemorySpec<'a>>,
globals_spec: Vec<GlobalSpec<'a>>,
function_info: Vec<FunctionMetadata<'a>>,
import_functions: Vec<ImportFunction<'a>>,
export_functions: Vec<ExportFunction<'a>>,
signatures: Vec<Signature>,
) -> Self {
let module_signature = vec![0u8; 0];
Self {
linear_memory,
globals_spec,
function_info,
import_functions,
export_functions,
signatures,
module_signature,
}
}

pub fn heap_spec(&self) -> Option<&HeapSpec> {
if let Some(ref linear_memory) = self.linear_memory {
Some(&linear_memory.heap)
Expand Down Expand Up @@ -113,6 +136,7 @@ impl<'a> ModuleData<'a> {
&self.module_signature
}

#[cfg(feature = "signature_checking")]
pub fn patch_module_signature(
module_data_bin: &'a [u8],
module_signature: &[u8],
Expand All @@ -127,6 +151,7 @@ impl<'a> ModuleData<'a> {
Ok(patched_module_data_bin)
}

#[cfg(feature = "signature_checking")]
pub fn clear_module_signature(module_data_bin: &'a [u8]) -> Result<Vec<u8>, Error> {
let module_signature = vec![0u8; SignatureBones::BYTES];
Self::patch_module_signature(module_data_bin, &module_signature)
Expand Down
11 changes: 10 additions & 1 deletion lucet-module/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
#[cfg(feature = "signature_checking")]
use crate::error::Error::{self, IOError, ModuleSignatureError};
use crate::module::LUCET_MODULE_SYM;
use crate::module_data::MODULE_DATA_SYM;
#[cfg(feature = "signature_checking")]
use crate::ModuleData;
use byteorder::{ByteOrder, LittleEndian};
#[cfg(feature = "signature_checking")]
pub use minisign::{PublicKey, SecretKey};
#[cfg(feature = "signature_checking")]
use minisign::{SignatureBones, SignatureBox};
use object::*;
use std::fs::{File, OpenOptions};
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
#[cfg(feature = "signature_checking")]
use std::io::Cursor;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::Path;

pub struct ModuleSignature;

#[cfg(feature = "signature_checking")]
impl ModuleSignature {
pub fn verify<P: AsRef<Path>>(
so_path: P,
Expand Down Expand Up @@ -68,12 +75,14 @@ struct SymbolData {
len: usize,
}

#[allow(dead_code)]
struct RawModuleAndData {
pub obj_bin: Vec<u8>,
pub module_data_offset: usize,
pub module_data_len: usize,
}

#[allow(dead_code)]
impl RawModuleAndData {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let mut obj_bin: Vec<u8> = Vec::new();
Expand Down
3 changes: 3 additions & 0 deletions lucet-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ tempfile = "3.0"
# only used for tests
cc = "1.0"

[features]
signature_checking = ["lucet-module/signature_checking"]

[lib]
name = "lucet_runtime"
crate-type = ["rlib", "staticlib", "cdylib"]
Expand Down
3 changes: 3 additions & 0 deletions lucet-runtime/lucet-runtime-internals/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ byteorder = "1.2"

[build-dependencies]
cc = "1.0"

[features]
signature_checking = ["lucet-module/signature_checking"]
40 changes: 29 additions & 11 deletions lucet-runtime/lucet-runtime-internals/src/module/dl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::module::{AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, T
use libc::c_void;
use libloading::Library;
use lucet_module::{
FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleSignature,
PublicKey, SerializedModule, Signature, LUCET_MODULE_SYM,
FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, SerializedModule,
Signature, LUCET_MODULE_SYM,
};
#[cfg(feature = "signature_checking")]
use lucet_module::{ModuleSignature, PublicKey};
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::path::Path;
Expand All @@ -31,26 +33,47 @@ unsafe impl Sync for DlModule {}
impl DlModule {
/// Create a module, loading code from a shared object on the filesystem.
pub fn load<P: AsRef<Path>>(so_path: P) -> Result<Arc<Self>, Error> {
Self::load_and_maybe_verify(so_path, None)
Self::load_and_maybe_verify(so_path, |_module_data| Ok(()))
}

/// Create a module, loading code from a shared object on the filesystem
/// and verifying it using a public key if one has been supplied.
#[cfg(feature = "signature_checking")]
pub fn load_and_verify<P: AsRef<Path>>(so_path: P, pk: PublicKey) -> Result<Arc<Self>, Error> {
Self::load_and_maybe_verify(so_path, Some(pk))
Self::load_and_maybe_verify(so_path, |module_data| {
// Public key has been provided, verify the module signature
// The TOCTOU issue is unavoidable without reimplenting `dlopen(3)`
ModuleSignature::verify(so_path, &pk, &module_data)
})
}

fn load_and_maybe_verify<P: AsRef<Path>>(
so_path: P,
pk: Option<PublicKey>,
verifier: fn(&ModuleData) -> Result<(), Error>,
// pk: Option<PublicKey>,
) -> Result<Arc<Self>, Error> {
// Load the dynamic library. The undefined symbols corresponding to the lucet_syscall_
// functions will be provided by the current executable. We trust our wasm->dylib compiler
// to make sure these function calls are the way the dylib can touch memory outside of its
// stack and heap.
let abs_so_path = so_path.as_ref().canonicalize().map_err(Error::DlError)?;
let lib = Library::new(abs_so_path.as_os_str()).map_err(Error::DlError)?;
return Self::parse_and_maybe_verify(lib, verifier);
}

/// Use the WASM module statically linked into the current binary
/// Currently Unix only as LibLoading does not support getting a handle to the running executable on windows
#[cfg(unix)]
pub fn from_current_binary() -> Result<Arc<Self>, Error> {
let curr = libloading::os::unix::Library::this();
let lib = Library::from(curr);
return Self::parse_and_maybe_verify(lib, |_module_data| Ok(()));
}

fn parse_and_maybe_verify(
lib: Library,
verifier: fn(&ModuleData) -> Result<(), Error>,
) -> Result<Arc<Self>, Error> {
let serialized_module_ptr = unsafe {
lib.get::<*const SerializedModule>(LUCET_MODULE_SYM.as_bytes())
.map_err(|e| {
Expand All @@ -75,12 +98,7 @@ impl DlModule {
)
};
let module_data = ModuleData::deserialize(module_data_slice)?;

// If a public key has been provided, verify the module signature
// The TOCTOU issue is unavoidable without reimplenting `dlopen(3)`
if let Some(pk) = pk {
ModuleSignature::verify(so_path, &pk, &module_data)?;
}
verifier(&module_data)?;

let fbase = if let Some(dli) =
dladdr(serialized_module as *const SerializedModule as *const c_void)
Expand Down
4 changes: 3 additions & 1 deletion lucet-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,9 @@

pub mod c_api;

pub use lucet_module::{PublicKey, TrapCode};
#[cfg(feature = "signature_checking")]
pub use lucet_module::PublicKey;
pub use lucet_module::TrapCode;
pub use lucet_runtime_internals::alloc::Limits;
pub use lucet_runtime_internals::error::Error;
pub use lucet_runtime_internals::instance::{
Expand Down
45 changes: 31 additions & 14 deletions lucet-wasi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ extern crate clap;

use clap::Arg;
use failure::{format_err, Error};
use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, PublicKey, Region, RunResult};
#[cfg(feature = "signature_checking")]
use lucet_runtime::PublicKey;
use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, Region, RunResult};
use lucet_wasi::{hostcalls, WasiCtxBuilder};
use std::fs::File;
use std::path::PathBuf;
Expand All @@ -18,6 +20,7 @@ struct Config<'a> {
preopen_dirs: Vec<(File, &'a str)>,
limits: Limits,
verify: bool,
#[allow(dead_code)]
pk_path: Option<PathBuf>,
}

Expand Down Expand Up @@ -191,23 +194,37 @@ fn main() {
run(config)
}

#[cfg(feature = "signature_checking")]
fn get_module(config: &Config<'_>) -> Arc<DlModule> {
let pk = match (config.verify, config.pk_path) {
(false, _) => None,
(true, Some(pk_path)) => {
Some(PublicKey::from_file(pk_path).expect("public key can be loaded"))
}
(true, None) => panic!("signature verification requires a public key"),
};
let module = if let Some(pk) = pk {
DlModule::load_and_verify(&config.lucet_module, pk).expect("signed module can be loaded")
} else {
DlModule::load(&config.lucet_module).expect("module can be loaded")
};
return module;
}

#[cfg(not(feature = "signature_checking"))]
fn get_module(config: &Config<'_>) -> Arc<DlModule> {
if config.verify {
panic!("Lucet not compiled with signature verification support");
}
let module = DlModule::load(&config.lucet_module).expect("module can be loaded");
return module;
}

fn run(config: Config<'_>) {
lucet_wasi::hostcalls::ensure_linked();
let exitcode = {
// doing all of this in a block makes sure everything gets dropped before exiting
let pk = match (config.verify, config.pk_path) {
(false, _) => None,
(true, Some(pk_path)) => {
Some(PublicKey::from_file(pk_path).expect("public key can be loaded"))
}
(true, None) => panic!("signature verification requires a public key"),
};
let module = if let Some(pk) = pk {
DlModule::load_and_verify(&config.lucet_module, pk)
.expect("signed module can be loaded")
} else {
DlModule::load(&config.lucet_module).expect("module can be loaded")
};
let module = get_module(&config);
let min_globals_size = module.initial_globals_size();
let globals_size = ((min_globals_size + 4096 - 1) / 4096) * 4096;

Expand Down
3 changes: 3 additions & 0 deletions lucetc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ human-size = "0.4"
parity-wasm = "0.38"
minisign = "0.5.11"
memoffset = "0.5.1"

[features]
signature_checking = ["lucet-module/signature_checking"]
2 changes: 2 additions & 0 deletions lucetc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ pub enum LucetcErrorKind {
Signature,
#[fail(display = "Unsupported")]
Unsupported,
#[fail(display = "Module Signing disabled at compile time")]
NoSigningSupport,
}
8 changes: 8 additions & 0 deletions lucetc/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::error::LucetcErrorKind::NoSigningSupport;
use failure::*;
#[cfg(feature = "signature_checking")]
use lucet_module::ModuleSignature;
pub use minisign::{KeyPair, PublicKey, SecretKey, SignatureBones, SignatureBox};
use std::fs::File;
Expand Down Expand Up @@ -74,6 +76,12 @@ pub fn verify_source_code(
}

// Sign the compiled code
#[cfg(feature = "signature_checking")]
pub fn sign_module<P: AsRef<Path>>(path: P, sk: &SecretKey) -> Result<(), Error> {
ModuleSignature::sign(path, sk).map_err(|e| e.into())
}

#[cfg(not(feature = "signature_checking"))]
pub fn sign_module<P: AsRef<Path>>(_path: P, _sk: &SecretKey) -> Result<(), Error> {
Err(NoSigningSupport).map_err(|e| e.into())
}