wasmerio/wasmer · critical
unable to make memory readonly and executable
Error message
unable to make memory readonly and executable
What it means
After compiling code into code_memory, publish() makes previously-written pages non-writable by changing protection from RW to READ_EXECUTE via the `region` crate; it uses .expect() because making JIT memory executable is considered essential. A failure means the OS refused mprotect/VirtualProtect on the JIT region.
Source
Thrown at lib/compiler/src/engine/code_memory.rs:173
executable_section_result,
data_section_result,
))
}
/// Apply the page permissions.
pub fn publish(&mut self) {
if self.mmap.is_empty() || self.start_of_nonexecutable_pages == 0 {
return;
}
assert!(self.mmap.len() >= self.start_of_nonexecutable_pages);
unsafe {
region::protect(
self.mmap.as_mut_ptr(),
self.start_of_nonexecutable_pages,
region::Protection::READ_EXECUTE,
)
}
.expect("unable to make memory readonly and executable");
}
/// Calculates the allocation size of the given compiled function.
fn function_allocation_size<'a>(func: &'a impl FunctionBodyLike<'a>) -> usize {
match &func.unwind_info().map(|o| o.get()) {
Some(CompiledFunctionUnwindInfoReference::WindowsX64(info)) => {
// Windows unwind information is required to be emitted into code memory
// This is because it must be a positive relative offset from the start of the memory
// Account for necessary unwind information alignment padding (32-bit alignment)
func.body().len().next_multiple_of(4) + info.len()
}
_ => func.body().len(),
}
}
/// Copies the data of the compiled function to the given buffer.
///
/// This will also add the function to the current function table.View on GitHub (pinned to 8c4b9ee9d3)
Solutions
- Relax the security policy: allow execmem in SELinux (`setsebool` or adjust policy) or seccomp profile for the container
- Disable strict W^X enforcement (kernel.pax.softmode or hardened kernel option) if policy permits
- Run in an environment that allows RWX/RT→RX transitions for JIT memory
- Upgrade wasmer; if the failure is caused by an alignment/length bug, newer versions may fix it
Defensive patterns
Strategy: retry
Validate before calling
// Precheck: try an mprotect on a scratch RWX page before JIT compilation
let test = region::protect(test_page, 4096, region::Protection::READ_EXECUTE);
if test.is_err() { return Err(anyhow!("environment blocks executable memory (execmem/W^X)")); } Try / catch
std::panic::catch_unwind(|| code_memory.publish())
.map_err(|_| anyhow::anyhow!("JIT memory protection failed; environment forbids executable pages"))? Prevention
- Verify the container/SELinux/seccomp policy allows PROT_EXEC allocations before deploying JIT workloads
- Run with a profile that permits execmem (e.g. docker seccomp unconfined for the compiler service)
- Test deployment hosts for hardened W^X kernels (PaX/grsecurity) early
- Keep a sys-jit-free fallback (interpreter/dylib artifact loading) if policies are strict
When it happens
Trigger: Calling publish() (during module compilation/artifact finalization) when mprotect fails: page-alignment mismatch, mmap region already protected differently, SELinux/AppArmor restrictions, hardened kernels with W^X enforcement (e.g. PaX), or out-of-memory/address-space conditions.
Common situations: Environments with SELinux denying execmem, Docker/seccomp profiles blocking mprotect with PROT_EXEC, kernels with strict W^X policies, or running under hardened security modules while JIT-compiling.
Related errors
- Unsupported libcall
- The relocation {reloc} is not yet supported.
- failed to register unwind information
- only numeric types are supported in function signatures
- Global index must be valid
AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01).
Data as JSON: /api/errors/59994501286a9f11.
Report an issue: GitHub.