From 8d32008808ad89a62394944ef99228d982d3be26 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 16 Oct 2024 11:21:36 -0500 Subject: [PATCH] Fix an timeout in fuzzing (#9475) This commit fixes a timeout that was found by OSS-Fuzz recently where a module was calling `memory.grow` many times, presumably in a loop, with a modest amount each time. This meant that `memory.grow` was taking, in total, a quadratic amount of time because Wasmtime was configured with dynamic memories and no memory was reserved for growth. That in turn meant that the test case eventually timed out due to this quadratic behavior. To fix this in addition to the memory allocation cap that we already track a new cap for the number of times memories/tables can be grown was also added. Any growth beyond this limit is rejected and helps prevent this quadratic behavior. --- crates/fuzzing/src/oracles.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 1d2d4f5ff41..b76b021e526 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -70,6 +70,8 @@ pub struct StoreLimits(Arc); struct LimitsState { /// Remaining memory, in bytes, left to allocate remaining_memory: AtomicUsize, + /// Remaining times memories/tables can be grown + remaining_growths: AtomicUsize, /// Whether or not an allocation request has been denied oom: AtomicBool, } @@ -81,12 +83,30 @@ impl StoreLimits { // Limits tables/memories within a store to at most 1gb for now to // exercise some larger address but not overflow various limits. remaining_memory: AtomicUsize::new(1 << 30), + // Also limit the number of times a memory or table may be grown. + // Otherwise infinite growths can exhibit quadratic behavior. For + // example Wasmtime could be configured with dynamic memories and no + // guard regions to grow into, meaning each memory growth could be a + // `memcpy`. As more data is added over time growths get more and + // more expensive meaning that fuel may not be effective at limiting + // execution time. + remaining_growths: AtomicUsize::new(100), oom: AtomicBool::new(false), })) } fn alloc(&mut self, amt: usize) -> bool { log::trace!("alloc {amt:#x} bytes"); + if self + .0 + .remaining_growths + .fetch_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(1)) + .is_err() + { + self.0.oom.store(true, SeqCst); + log::debug!("too many growths, rejecting allocation"); + return false; + } match self .0 .remaining_memory