From 6e4a0b6f5987b350254769ef3ab611069e9db028 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 29 Mar 2019 09:49:20 -0700 Subject: [PATCH 1/4] [lucet-wasi] Add CLI configuration for memory limits Closes #74 and #76 --- Cargo.lock | 1 + .../lucet-runtime-internals/src/module.rs | 7 ++ lucet-wasi/Cargo.toml | 1 + lucet-wasi/src/main.rs | 71 ++++++++++++++++++- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dec24c167..80ccc074d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,7 @@ dependencies = [ "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "lucet-runtime 0.1.0", "lucet-runtime-internals 0.1.0", diff --git a/lucet-runtime/lucet-runtime-internals/src/module.rs b/lucet-runtime/lucet-runtime-internals/src/module.rs index 9c7f5a852..6722df0ca 100644 --- a/lucet-runtime/lucet-runtime-internals/src/module.rs +++ b/lucet-runtime/lucet-runtime-internals/src/module.rs @@ -164,6 +164,13 @@ pub trait ModuleInternal: Send + Sync { bail_limits_exceeded!("heap spec initial size: {:?}", heap); } + if heap.initial_size > heap.reserved_size { + return Err(lucet_incorrect_module!( + "initial heap size greater than reserved size: {:?}", + heap + )); + } + if self.globals().len() * std::mem::size_of::() > limits.globals_size { bail_limits_exceeded!("globals exceed limits"); } diff --git a/lucet-wasi/Cargo.toml b/lucet-wasi/Cargo.toml index 91625aeef..b20379ee2 100644 --- a/lucet-wasi/Cargo.toml +++ b/lucet-wasi/Cargo.toml @@ -9,6 +9,7 @@ license = "Apache-2.0 WITH LLVM-exception" cast = "0.2" clap = "2.23" failure = "0.1" +human-size = "0.4" libc = "0.2" lucet-runtime = { path = "../lucet-runtime" } lucet-runtime-internals = { path = "../lucet-runtime/lucet-runtime-internals" } diff --git a/lucet-wasi/src/main.rs b/lucet-wasi/src/main.rs index 6eae016b7..518517225 100644 --- a/lucet-wasi/src/main.rs +++ b/lucet-wasi/src/main.rs @@ -2,6 +2,7 @@ extern crate clap; use clap::Arg; +use human_size::{Byte, Size}; use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, Region}; use lucet_wasi::{hostcalls, WasiCtxBuilder}; use std::fs::File; @@ -12,6 +13,7 @@ struct Config<'a> { guest_args: Vec<&'a str>, entrypoint: &'a str, preopen_dirs: Vec<(File, &'a str)>, + limits: Limits, } fn main() { @@ -41,10 +43,10 @@ fn main() { virtual filesystem. Each directory is specified as \ --dir `host_path:guest_path`, where `guest_path` specifies the path that will \ correspond to `host_path` for calls like `fopen` in the guest.\ - \ + \n\n\ For example, `--dir /home/host_user/wasi_sandbox:/sandbox` will make \ `/home/host_user/wasi_sandbox` available within the guest as `/sandbox`.\ - \ + \n\n\ Guests will be able to access any files and directories under the \ `host_path`, but will be unable to access other parts of the host \ filesystem through relative paths (e.g., `/sandbox/../some_other_file`) \ @@ -56,6 +58,39 @@ fn main() { .required(true) .help("Path to the `lucetc`-compiled WASI module"), ) + .arg( + Arg::with_name("heap_memory_size") + .long("max-heap-size") + .takes_value(true) + .default_value("1024 KiB") + .help("Maximum heap size (must be a multiple of 4 KiB)"), + ) + .arg( + Arg::with_name("heap_address_space_size") + .long("heap-address-space") + .takes_value(true) + .default_value("8 GiB") + .help("Maximum heap address space size (must be a multiple of 4 KiB)") + .long_help( + "Maximum heap address space size. Must be a multiple of 4KiB, and \ + must be least as large as `max-heap-size`, `stack-size`, and \ + `globals-size`, combined.", + ), + ) + .arg( + Arg::with_name("stack_size") + .long("stack-size") + .takes_value(true) + .default_value("128 KiB") + .help("Maximum stack size (must be a multiple of 4 KiB)"), + ) + .arg( + Arg::with_name("globals_size") + .long("globals-size") + .takes_value(true) + .default_value("4 KiB") + .help("Maximum globals size (must be a multiple of 4 KiB)"), + ) .arg( Arg::with_name("guest_args") .required(false) @@ -65,7 +100,9 @@ fn main() { .get_matches(); let entrypoint = matches.value_of("entrypoint").unwrap(); + let lucet_module = matches.value_of("lucet_module").unwrap(); + let preopen_dirs = matches .values_of("preopen_dirs") .map(|vals| { @@ -84,16 +121,44 @@ fn main() { .collect() }) .unwrap_or(vec![]); + + let heap_memory_size = value_t!(matches, "heap_memory_size", Size) + .unwrap_or_else(|e| e.exit()) + .into::() + .value() as usize; + let heap_address_space_size = value_t!(matches, "heap_address_space_size", Size) + .unwrap_or_else(|e| e.exit()) + .into::() + .value() as usize; + let stack_size = value_t!(matches, "stack_size", Size) + .unwrap_or_else(|e| e.exit()) + .into::() + .value() as usize; + let globals_size = value_t!(matches, "globals_size", Size) + .unwrap_or_else(|e| e.exit()) + .into::() + .value() as usize; + + let limits = Limits { + heap_memory_size, + heap_address_space_size, + stack_size, + globals_size, + }; + let guest_args = matches .values_of("guest_args") .map(|vals| vals.collect()) .unwrap_or(vec![]); + let config = Config { lucet_module, guest_args, entrypoint, preopen_dirs, + limits, }; + run(config) } @@ -101,7 +166,7 @@ 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 region = MmapRegion::create(1, &Limits::default()).expect("region can be created"); + let region = MmapRegion::create(1, &config.limits).expect("region can be created"); let module = DlModule::load(&config.lucet_module).expect("module can be loaded"); // put the path to the module on the front for argv[0] From fe8aa609e9a27a0c2d32f3b86809f6615982d3a2 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 29 Mar 2019 11:15:05 -0700 Subject: [PATCH 2/4] [lucet-runtime] make init/reset faster for large initial heaps Rather than explicitly zeroing, use `madvise` and `mprotect` to zero the heap. We also now make sure the heap address space is at least as big as the memory size when validating `Limits`. --- .../lucet-runtime-internals/src/alloc/mod.rs | 5 ++ .../src/alloc/tests.rs | 13 +++++ .../src/region/mmap.rs | 49 +++++++++---------- lucet-wasi/src/main.rs | 14 +++--- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs b/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs index 084801512..d9d7bb55a 100644 --- a/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs +++ b/lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs @@ -388,6 +388,11 @@ impl Limits { "address space size must be a multiple of host page size", )); } + if self.heap_memory_size > self.heap_address_space_size { + return Err(Error::InvalidArgument( + "address space size must be at least as large as memory size", + )); + } if self.stack_size % host_page_size() != 0 { return Err(Error::InvalidArgument( "stack size must be a multiple of host page size", diff --git a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs index 746cfff11..212fb4d84 100644 --- a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs +++ b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs @@ -256,6 +256,19 @@ macro_rules! alloc_tests { assert!(res.is_err(), "new_instance fails"); } + /// This test shows that we reject limits with a larger memory size than address space size + #[test] + fn reject_undersized_address_space() { + const LIMITS: Limits = Limits { + heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096, + heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE, + stack_size: LIMITS_STACK_SIZE, + globals_size: LIMITS_GLOBALS_SIZE, + }; + let res = TestRegion::create(10, &LIMITS); + assert!(res.is_err(), "region creation fails"); + } + const SMALL_GUARD_HEAP: HeapSpec = HeapSpec { reserved_size: SPEC_HEAP_RESERVED_SIZE, guard_size: SPEC_HEAP_GUARD_SIZE - 1, diff --git a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs index b895963b4..2d96d570f 100644 --- a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs +++ b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs @@ -39,8 +39,6 @@ impl RegionInternal for MmapRegion { module.validate_runtime_spec(limits)?; for (ptr, len) in [ - // make the heap read/writable and record its initial size - (slot.heap, module.heap_spec().initial_size as usize), // make the stack read/writable (slot.stack, limits.stack_size), // make the globals read/writable @@ -54,6 +52,8 @@ impl RegionInternal for MmapRegion { unsafe { mprotect(*ptr, *len, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE)? }; } + // note: the initial heap will be made read/writable when `new_instance_handle` calls `reset` + let inst_ptr = slot.start as *mut Instance; // upgrade the slot's weak region pointer so the region can't get dropped while the instance @@ -66,7 +66,7 @@ impl RegionInternal for MmapRegion { .expect("backing region of slot (`self`) exists"); let alloc = Alloc { - heap_accessible_size: module.heap_spec().initial_size as usize, + heap_accessible_size: 0, // the `reset` call in `new_instance_handle` will set this heap_inaccessible_size: slot.limits.heap_address_space_size, slot: Some(slot), region, @@ -119,29 +119,32 @@ impl RegionInternal for MmapRegion { } fn reset_heap(&self, alloc: &mut Alloc, module: &dyn Module) -> Result<(), Error> { + let heap = alloc.slot().heap; + + if alloc.heap_accessible_size > 0 { + // zero the whole heap, if any of it is currently accessible + let heap_size = alloc.slot().limits.heap_address_space_size; + + unsafe { + mprotect(heap, heap_size, ProtFlags::PROT_NONE)?; + madvise(heap, heap_size, MmapAdvise::MADV_DONTNEED)?; + } + } + let initial_size = module.heap_spec().initial_size as usize; - // reset the heap to the initial size + // reset the heap to the initial size, and mprotect those pages appropriately if alloc.heap_accessible_size != initial_size { + unsafe { + mprotect( + heap, + initial_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + )? + }; alloc.heap_accessible_size = initial_size; alloc.heap_inaccessible_size = alloc.slot().limits.heap_address_space_size - initial_size; - - // turn off any extra pages - let acc_heap_end = - (alloc.slot().heap as usize + alloc.heap_accessible_size) as *mut c_void; - unsafe { - mprotect( - acc_heap_end, - alloc.heap_inaccessible_size, - ProtFlags::PROT_NONE, - )?; - madvise( - acc_heap_end, - alloc.heap_inaccessible_size, - MmapAdvise::MADV_DONTNEED, - )?; - } } // Initialize the heap using the module sparse page data. There cannot be more pages in the @@ -171,12 +174,6 @@ impl RegionInternal for MmapRegion { if let Some(contents) = module.get_sparse_page_data(page_num) { // otherwise copy in the page data heap[page_base..page_base + host_page_size()].copy_from_slice(contents); - } else { - // zero this page - // TODO would using a memset primitive be better here? - for b in heap[page_base..page_base + host_page_size()].iter_mut() { - *b = 0x00; - } } } diff --git a/lucet-wasi/src/main.rs b/lucet-wasi/src/main.rs index 518517225..4c5495515 100644 --- a/lucet-wasi/src/main.rs +++ b/lucet-wasi/src/main.rs @@ -70,12 +70,7 @@ fn main() { .long("heap-address-space") .takes_value(true) .default_value("8 GiB") - .help("Maximum heap address space size (must be a multiple of 4 KiB)") - .long_help( - "Maximum heap address space size. Must be a multiple of 4KiB, and \ - must be least as large as `max-heap-size`, `stack-size`, and \ - `globals-size`, combined.", - ), + .help("Maximum heap address space size (must be a multiple of 4 KiB, and >= `max-heap-size`)"), ) .arg( Arg::with_name("stack_size") @@ -130,6 +125,13 @@ fn main() { .unwrap_or_else(|e| e.exit()) .into::() .value() as usize; + + if heap_memory_size > heap_address_space_size { + println!("`heap-address-space` must be at least as large as `max-heap-size`"); + println!("{}", matches.usage()); + std::process::exit(1); + } + let stack_size = value_t!(matches, "stack_size", Size) .unwrap_or_else(|e| e.exit()) .into::() From 70d730e20b0369b1237b74f4c6fc9097759cb233 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 29 Mar 2019 11:47:30 -0700 Subject: [PATCH 3/4] [lucet-wasi] increase default memory limits - Heap limits are set to maximum for a 32-bit address space + 32-bit guard size - Stack size is set to 8MiB to match Linux's default - Globals size is calculated from the module --- lucet-wasi/src/main.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lucet-wasi/src/main.rs b/lucet-wasi/src/main.rs index 4c5495515..eb9837c6b 100644 --- a/lucet-wasi/src/main.rs +++ b/lucet-wasi/src/main.rs @@ -4,6 +4,7 @@ extern crate clap; use clap::Arg; use human_size::{Byte, Size}; use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, Region}; +use lucet_runtime_internals::module::ModuleInternal; use lucet_wasi::{hostcalls, WasiCtxBuilder}; use std::fs::File; use std::sync::Arc; @@ -62,7 +63,7 @@ fn main() { Arg::with_name("heap_memory_size") .long("max-heap-size") .takes_value(true) - .default_value("1024 KiB") + .default_value("4 GiB") .help("Maximum heap size (must be a multiple of 4 KiB)"), ) .arg( @@ -76,16 +77,9 @@ fn main() { Arg::with_name("stack_size") .long("stack-size") .takes_value(true) - .default_value("128 KiB") + .default_value("8 MiB") .help("Maximum stack size (must be a multiple of 4 KiB)"), ) - .arg( - Arg::with_name("globals_size") - .long("globals-size") - .takes_value(true) - .default_value("4 KiB") - .help("Maximum globals size (must be a multiple of 4 KiB)"), - ) .arg( Arg::with_name("guest_args") .required(false) @@ -136,16 +130,12 @@ fn main() { .unwrap_or_else(|e| e.exit()) .into::() .value() as usize; - let globals_size = value_t!(matches, "globals_size", Size) - .unwrap_or_else(|e| e.exit()) - .into::() - .value() as usize; let limits = Limits { heap_memory_size, heap_address_space_size, stack_size, - globals_size, + globals_size: 0, // calculated from module }; let guest_args = matches @@ -168,8 +158,18 @@ 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 region = MmapRegion::create(1, &config.limits).expect("region can be created"); let module = DlModule::load(&config.lucet_module).expect("module can be loaded"); + let min_globals_size = module.globals().len() * std::mem::size_of::(); + let globals_size = ((min_globals_size + 4096 - 1) / 4096) * 4096; + + let region = MmapRegion::create( + 1, + &Limits { + globals_size, + ..config.limits + }, + ) + .expect("region can be created"); // put the path to the module on the front for argv[0] let args = std::iter::once(config.lucet_module) From b563370b6b9e1b5821f2bfe288ec956da953638f Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 29 Mar 2019 13:07:06 -0700 Subject: [PATCH 4/4] [lucetc] tweak treatment of reserved size Instead of a single `reserved-size` argument, `lucetc` now optionally takes `--min-reserved-size` and `--max-reserved-size`. By default, the minimum is 4 KiB, and the maximum is 6 GiB (the entire possible wasm address space, plus extra room for bounds check elision). In the case that the module's initial heap size is larger than the minimum, we use the initial heap size as the reserved size. In the case that the initial size is larger than the maximum, compilation fails. --- .../src/alloc/tests.rs | 12 +++---- lucetc/src/compiler/data.rs | 2 +- lucetc/src/compiler/entity/mod.rs | 2 +- lucetc/src/compiler/memory.rs | 2 +- lucetc/src/compiler/module_data.rs | 4 +-- lucetc/src/lib.rs | 16 +++++++--- lucetc/src/main.rs | 8 +++-- lucetc/src/options.rs | 31 ++++++++++++++----- lucetc/src/program/memory.rs | 27 ++++++++++++---- lucetc/src/program/mod.rs | 10 +++--- lucetc/tests/wasm.rs | 6 ++-- 11 files changed, 82 insertions(+), 38 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs index 212fb4d84..603f8ef92 100644 --- a/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs +++ b/lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs @@ -259,12 +259,12 @@ macro_rules! alloc_tests { /// This test shows that we reject limits with a larger memory size than address space size #[test] fn reject_undersized_address_space() { - const LIMITS: Limits = Limits { - heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096, - heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE, - stack_size: LIMITS_STACK_SIZE, - globals_size: LIMITS_GLOBALS_SIZE, - }; + const LIMITS: Limits = Limits { + heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096, + heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE, + stack_size: LIMITS_STACK_SIZE, + globals_size: LIMITS_GLOBALS_SIZE, + }; let res = TestRegion::create(10, &LIMITS); assert!(res.is_err(), "region creation fails"); } diff --git a/lucetc/src/compiler/data.rs b/lucetc/src/compiler/data.rs index bec0a74ec..4d84df8b7 100644 --- a/lucetc/src/compiler/data.rs +++ b/lucetc/src/compiler/data.rs @@ -77,7 +77,7 @@ pub fn compile_sparse_page_data(compiler: &mut Compiler) -> Result<(), Error> { use crate::program::data::sparse::OwnedSparseData; let owned_data = OwnedSparseData::new( &compiler.prog.data_initializers()?, - compiler.prog.heap_spec(), + compiler.prog.heap_spec()?, ); let sparse_data = owned_data.sparse_data(); diff --git a/lucetc/src/compiler/entity/mod.rs b/lucetc/src/compiler/entity/mod.rs index 3645a68ae..32b97125b 100644 --- a/lucetc/src/compiler/entity/mod.rs +++ b/lucetc/src/compiler/entity/mod.rs @@ -76,7 +76,7 @@ impl<'p> EntityCreator<'p> { compiler: &Compiler, ) -> Result { let base = self.bases.heap(func, compiler); - let heap_spec = self.program.heap_spec(); + let heap_spec = self.program.heap_spec()?; self.cache.heap(index, || { if index != 0 { diff --git a/lucetc/src/compiler/memory.rs b/lucetc/src/compiler/memory.rs index 9dd2ad5ef..c71614e34 100644 --- a/lucetc/src/compiler/memory.rs +++ b/lucetc/src/compiler/memory.rs @@ -5,7 +5,7 @@ use cranelift_module::{DataContext, Linkage}; use failure::Error; pub fn compile_memory_specs(compiler: &mut Compiler) -> Result<(), Error> { - let heap = compiler.prog.heap_spec(); + let heap = compiler.prog.heap_spec()?; let mut heap_spec_ctx = DataContext::new(); heap_spec_ctx.define(serialize_spec(&heap).into_boxed_slice()); diff --git a/lucetc/src/compiler/module_data.rs b/lucetc/src/compiler/module_data.rs index a0d05e0c5..6fa5280fa 100644 --- a/lucetc/src/compiler/module_data.rs +++ b/lucetc/src/compiler/module_data.rs @@ -7,10 +7,10 @@ use lucet_module_data::ModuleData; pub fn compile_module_data(compiler: &mut Compiler) -> Result<(), Error> { let module_data_serialized: Vec = { - let heap_spec = compiler.prog.heap_spec(); + let heap_spec = compiler.prog.heap_spec()?; let compiled_data = OwnedSparseData::new( &compiler.prog.data_initializers()?, - compiler.prog.heap_spec(), + compiler.prog.heap_spec()?, ); let sparse_data = compiled_data.sparse_data(); diff --git a/lucetc/src/lib.rs b/lucetc/src/lib.rs index 616ddc099..856c1af57 100644 --- a/lucetc/src/lib.rs +++ b/lucetc/src/lib.rs @@ -87,12 +87,20 @@ impl Lucetc { Ok(()) } - pub fn reserved_size(mut self, reserved_size: u64) -> Self { - self.with_reserved_size(reserved_size); + pub fn min_reserved_size(mut self, min_reserved_size: u64) -> Self { + self.with_min_reserved_size(min_reserved_size); self } - pub fn with_reserved_size(&mut self, reserved_size: u64) { - self.heap.reserved_size = reserved_size; + pub fn with_min_reserved_size(&mut self, min_reserved_size: u64) { + self.heap.min_reserved_size = min_reserved_size; + } + + pub fn max_reserved_size(mut self, max_reserved_size: u64) -> Self { + self.with_max_reserved_size(max_reserved_size); + self + } + pub fn with_max_reserved_size(&mut self, max_reserved_size: u64) { + self.heap.max_reserved_size = max_reserved_size; } pub fn guard_size(mut self, guard_size: u64) -> Self { diff --git a/lucetc/src/main.rs b/lucetc/src/main.rs index ef89d7131..28f116c21 100644 --- a/lucetc/src/main.rs +++ b/lucetc/src/main.rs @@ -50,8 +50,12 @@ pub fn run(opts: &Options) -> Result<(), Error> { c.with_builtins(builtins)?; } - if let Some(reserved_size) = opts.reserved_size { - c.with_reserved_size(reserved_size); + if let Some(min_reserved_size) = opts.min_reserved_size { + c.with_min_reserved_size(min_reserved_size); + } + + if let Some(max_reserved_size) = opts.max_reserved_size { + c.with_max_reserved_size(max_reserved_size); } if let Some(guard_size) = opts.guard_size { diff --git a/lucetc/src/options.rs b/lucetc/src/options.rs index 180db6ca5..3fa608185 100644 --- a/lucetc/src/options.rs +++ b/lucetc/src/options.rs @@ -36,7 +36,8 @@ pub struct Options { pub codegen: CodegenOutput, pub binding_files: Vec, pub builtins_path: Option, - pub reserved_size: Option, + pub min_reserved_size: Option, + pub max_reserved_size: Option, pub guard_size: Option, pub opt_level: OptLevel, } @@ -67,8 +68,14 @@ impl Options { let builtins_path = m.value_of("builtins").map(PathBuf::from); - let reserved_size = if let Some(reserved_str) = m.value_of("reserved_size") { - Some(parse_humansized(reserved_str)?) + let min_reserved_size = if let Some(min_reserved_str) = m.value_of("min_reserved_size") { + Some(parse_humansized(min_reserved_str)?) + } else { + None + }; + + let max_reserved_size = if let Some(max_reserved_str) = m.value_of("max_reserved_size") { + Some(parse_humansized(max_reserved_str)?) } else { None }; @@ -93,7 +100,8 @@ impl Options { codegen, binding_files, builtins_path, - reserved_size, + min_reserved_size, + max_reserved_size, guard_size, opt_level, }) @@ -128,15 +136,22 @@ impl Options { .help("path to bindings json file"), ) .arg( - Arg::with_name("reserved_size") - .long("--reserved-size") + Arg::with_name("min_reserved_size") + .long("--min-reserved-size") .takes_value(true) .multiple(false) .help(&format!( - "size of usable linear memory region. must be multiple of 4k. default: {}", - humansized(HeapSettings::default().reserved_size) + "minimum size of usable linear memory region. must be multiple of 4k. default: {}", + humansized(HeapSettings::default().min_reserved_size) )), ) + .arg( + Arg::with_name("max_reserved_size") + .long("--max-reserved-size") + .takes_value(true) + .multiple(false) + .help("maximum size of usable linear memory region. must be multiple of 4k. default: 4 GiB"), + ) .arg( Arg::with_name("guard_size") .long("--guard-size") diff --git a/lucetc/src/program/memory.rs b/lucetc/src/program/memory.rs index 61470d422..81df83f9b 100644 --- a/lucetc/src/program/memory.rs +++ b/lucetc/src/program/memory.rs @@ -1,3 +1,5 @@ +use failure::{bail, Error}; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct MemorySpec { pub initial_pages: u32, @@ -6,32 +8,45 @@ pub struct MemorySpec { #[derive(Clone, Debug, PartialEq, Eq)] pub struct HeapSettings { - pub reserved_size: u64, + pub min_reserved_size: u64, + pub max_reserved_size: u64, pub guard_size: u64, } impl Default for HeapSettings { fn default() -> Self { Self { - reserved_size: 4 * 1024 * 1024, + min_reserved_size: 4 * 1024 * 1024, + max_reserved_size: 6 * 1024 * 1024 * 1024, guard_size: 4 * 1024 * 1024, } } } pub use lucet_module_data::HeapSpec; -pub fn create_heap_spec(mem: &MemorySpec, heap: &HeapSettings) -> HeapSpec { +pub fn create_heap_spec(mem: &MemorySpec, heap: &HeapSettings) -> Result { let wasm_page: u64 = 64 * 1024; let initial_size = mem.initial_pages as u64 * wasm_page; + + let reserved_size = std::cmp::max(initial_size, heap.min_reserved_size); + + if reserved_size > heap.max_reserved_size { + bail!( + "module reserved size ({}) exceeds max reserved size ({})", + initial_size, + heap.max_reserved_size, + ); + } + // Find the max size permitted by the heap and the memory spec let max_size = mem.max_pages.map(|pages| pages as u64 * wasm_page); - HeapSpec { - reserved_size: heap.reserved_size, + Ok(HeapSpec { + reserved_size, guard_size: heap.guard_size, initial_size: initial_size, max_size: max_size, - } + }) } pub fn empty_heap_spec() -> HeapSpec { HeapSpec { diff --git a/lucetc/src/program/mod.rs b/lucetc/src/program/mod.rs index ff4ecafa1..80143c10e 100644 --- a/lucetc/src/program/mod.rs +++ b/lucetc/src/program/mod.rs @@ -118,13 +118,15 @@ impl Program { self.runtime.get_symbol(name) } - pub fn heap_spec(&self) -> HeapSpec { + pub fn heap_spec(&self) -> Result { if let Some(ref mem_spec) = self.import_memory { - create_heap_spec(mem_spec, &self.heap_settings) + Ok(create_heap_spec(mem_spec, &self.heap_settings) + .context(LucetcErrorKind::MemorySpecs)?) } else if let Some(ref mem_spec) = self.defined_memory { - create_heap_spec(mem_spec, &self.heap_settings) + Ok(create_heap_spec(mem_spec, &self.heap_settings) + .context(LucetcErrorKind::MemorySpecs)?) } else { - empty_heap_spec() + Ok(empty_heap_spec()) } } diff --git a/lucetc/tests/wasm.rs b/lucetc/tests/wasm.rs index 6f32aa9a4..bf896b390 100644 --- a/lucetc/tests/wasm.rs +++ b/lucetc/tests/wasm.rs @@ -180,7 +180,7 @@ mod programs { let h = HeapSettings::default(); let p = Program::new(m, b, h).expect(&format!("instantiating program")); assert_eq!( - p.heap_spec(), + p.heap_spec().unwrap(), HeapSpec { // reserved and guard is liblucet_runtime_c standard reserved_size: 4 * 1024 * 1024, @@ -201,7 +201,7 @@ mod programs { let h = HeapSettings::default(); let p = Program::new(m, b, h).expect(&format!("instantiating program")); assert_eq!( - p.heap_spec(), + p.heap_spec().unwrap(), HeapSpec { // reserved and guard is liblucet_runtime_c standard reserved_size: 4 * 1024 * 1024, @@ -222,7 +222,7 @@ mod programs { let h = HeapSettings::default(); let p = Program::new(m, b, h).expect(&format!("instantiating program")); assert_eq!( - p.heap_spec(), + p.heap_spec().unwrap(), HeapSpec { reserved_size: 0, guard_size: 0,