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

  1. Relax the security policy: allow execmem in SELinux (`setsebool` or adjust policy) or seccomp profile for the container
  2. Disable strict W^X enforcement (kernel.pax.softmode or hardened kernel option) if policy permits
  3. Run in an environment that allows RWX/RT→RX transitions for JIT memory
  4. 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

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


AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01). Data as JSON: /api/errors/59994501286a9f11. Report an issue: GitHub.