Skip to content

Commit

Permalink
Add guard pages to the front of linear memories (bytecodealliance#2977)
Browse files Browse the repository at this point in the history
* Add guard pages to the front of linear memories

This commit implements a safety feature for Wasmtime to place guard
pages before the allocation of all linear memories. Guard pages placed
after linear memories are typically present for performance (at least)
because it can help elide bounds checks. Guard pages before a linear
memory, however, are never strictly needed for performance or features.
The intention of a preceding guard page is to help insulate against bugs
in Cranelift or other code generators, such as CVE-2021-32629.

This commit adds a `Config::guard_before_linear_memory` configuration
option, defaulting to `true`, which indicates whether guard pages should
be present both before linear memories as well as afterwards. Guard
regions continue to be controlled by
`{static,dynamic}_memory_guard_size` methods.

The implementation here affects both on-demand allocated memories as
well as the pooling allocator for memories. For on-demand memories this
adjusts the size of the allocation as well as adjusts the calculations
for the base pointer of the wasm memory. For the pooling allocator this
will place a singular extra guard region at the very start of the
allocation for memories. Since linear memories in the pooling allocator
are contiguous every memory already had a preceding guard region in
memory, it was just the previous memory's guard region afterwards. Only
the first memory needed this extra guard.

I've attempted to write some tests to help test all this, but this is
all somewhat tricky to test because the settings are pretty far away
from the actual behavior. I think, though, that the tests added here
should help cover various use cases and help us have confidence in
tweaking the various `Config` settings beyond their defaults.

Note that this also contains a semantic change where
`InstanceLimits::memory_reservation_size` has been removed. Instead this
field is now inferred from the `static_memory_maximum_size` and guard
size settings. This should hopefully remove some duplication in these
settings, canonicalizing on the guard-size/static-size settings as the
way to control memory sizes and virtual reservations.

* Update config docs

* Fix a typo

* Fix benchmark

* Fix wasmtime-runtime tests

* Fix some more tests

* Try to fix uffd failing test

* Review items

* Tweak 32-bit defaults

Makes the pooling allocator a bit more reasonable by default on 32-bit
with these settings.
  • Loading branch information
alexcrichton authored Jun 18, 2021
1 parent d8d4bf8 commit 7ce4604
Show file tree
Hide file tree
Showing 17 changed files with 564 additions and 205 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ tracing-subscriber = "0.2.16"
wast = "36.0.0"
criterion = "0.3.4"
num_cpus = "1.13.0"
winapi = { version = "0.3.9", features = ['memoryapi'] }

[build-dependencies]
anyhow = "1.0.19"
Expand Down
5 changes: 1 addition & 4 deletions benches/thread_eager_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ fn test_setup() -> (Engine, Module) {
memory_pages: 1,
..Default::default()
},
instance_limits: InstanceLimits {
count: pool_count,
memory_reservation_size: 1,
},
instance_limits: InstanceLimits { count: pool_count },
});
let engine = Engine::new(&config).unwrap();

Expand Down
2 changes: 2 additions & 0 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
MemoryPlan {
style: MemoryStyle::Dynamic,
offset_guard_size,
pre_guard_size: _,
memory: _,
} => {
let heap_bound = func.create_global_value(ir::GlobalValueData::Load {
Expand All @@ -1276,6 +1277,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
MemoryPlan {
style: MemoryStyle::Static { bound },
offset_guard_size,
pre_guard_size: _,
memory: _,
} => (
Uimm64::new(offset_guard_size),
Expand Down
7 changes: 7 additions & 0 deletions crates/environ/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub struct MemoryPlan {
pub memory: Memory,
/// Our chosen implementation style.
pub style: MemoryStyle,
/// Chosen size of a guard page before the linear memory allocation.
pub pre_guard_size: u64,
/// Our chosen offset-guard size.
pub offset_guard_size: u64,
}
Expand All @@ -75,6 +77,11 @@ impl MemoryPlan {
memory,
style,
offset_guard_size,
pre_guard_size: if tunables.guard_before_linear_memory {
offset_guard_size
} else {
0
},
}
}
}
Expand Down
40 changes: 22 additions & 18 deletions crates/environ/src/tunables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,46 @@ pub struct Tunables {

/// Whether or not to treat the static memory bound as the maximum for unbounded heaps.
pub static_memory_bound_is_maximum: bool,

/// Whether or not linear memory allocations will have a guard region at the
/// beginning of the allocation in addition to the end.
pub guard_before_linear_memory: bool,
}

impl Default for Tunables {
fn default() -> Self {
Self {
#[cfg(target_pointer_width = "32")]
/// Size in wasm pages of the bound for static memories.
static_memory_bound: 0x4000,
// 64-bit has tons of address space to static memories can have 4gb
// address space reservations liberally by default, allowing us to
// help eliminate bounds checks.
//
// Coupled with a 2 GiB address space guard it lets us translate
// wasm offsets into x86 offsets as aggressively as we can.
#[cfg(target_pointer_width = "64")]
/// Size in wasm pages of the bound for static memories.
///
/// When we allocate 4 GiB of address space, we can avoid the
/// need for explicit bounds checks.
static_memory_bound: 0x1_0000,
#[cfg(target_pointer_width = "64")]
static_memory_offset_guard_size: 0x8000_0000,

// For 32-bit we scale way down to 10MB of reserved memory. This
// impacts performance severely but allows us to have more than a
// few instances running around.
#[cfg(target_pointer_width = "32")]
static_memory_bound: (10 * (1 << 20)) / crate::WASM_PAGE_SIZE,
#[cfg(target_pointer_width = "32")]
/// Size in bytes of the offset guard for static memories.
static_memory_offset_guard_size: 0x1_0000,
#[cfg(target_pointer_width = "64")]
/// Size in bytes of the offset guard for static memories.
///
/// Allocating 2 GiB of address space lets us translate wasm
/// offsets into x86 offsets as aggressively as we can.
static_memory_offset_guard_size: 0x8000_0000,

/// Size in bytes of the offset guard for dynamic memories.
///
/// Allocate a small guard to optimize common cases but without
/// wasting too much memory.
// Size in bytes of the offset guard for dynamic memories.
//
// Allocate a small guard to optimize common cases but without
// wasting too much memory.
dynamic_memory_offset_guard_size: 0x1_0000,

generate_native_debuginfo: false,
parse_wasm_debuginfo: true,
interruptable: false,
consume_fuel: false,
static_memory_bound_is_maximum: false,
guard_before_linear_memory: true,
}
}
}
2 changes: 2 additions & 0 deletions crates/fuzzing/src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub struct Config {
static_memory_maximum_size: Option<u32>,
static_memory_guard_size: Option<u32>,
dynamic_memory_guard_size: Option<u32>,
guard_before_linear_memory: bool,
}

impl Config {
Expand All @@ -82,6 +83,7 @@ impl Config {
.static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0).into())
.static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0).into())
.dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0).into())
.guard_before_linear_memory(self.guard_before_linear_memory)
.cranelift_nan_canonicalization(self.canonicalize_nans)
.cranelift_opt_level(self.opt_level.to_wasmtime())
.interruptable(self.interruptable)
Expand Down
Loading

0 comments on commit 7ce4604

Please sign in to comment.