Assessment reports>Radix>Medium findings>Memory resource exhaustion via untracked buffers
Category: Coding Mistakes

Memory resource exhaustion via untracked buffers

Medium Severity
Medium Impact
Low Likelihood

Description

A vulnerability in the VM's buffer-management system allows an attacker to exceed memory limits through a combination of key-value store operations and raw syscalls. The issue stems from the following mechanisms.

  1. First is the ability to store VM memory pages in a key-value store entry and retrieve them using raw syscalls, leading to an untracked memory allocation when the returned buffer entry is not immediately cleaned up.

fn materialize_duplicates(handle: u32, pages: &mut [u8]) {
    unsafe {
        // ... SBOR encoding setup ...
        wasm_api::kv_entry::kv_entry_write(handle, pages.as_ptr(), i + len);

        // Revert to initial value
        (pages.as_mut_ptr() as *mut u64).write_volatile(initial_value);
    }

    for _i in 0..31 { // buffer limit
        unsafe {
            std::mem::forget(wasm_api::kv_entry::kv_entry_read(handle));
        };
    }
}
  1. Second is the ability to recursively create new call frames up to the depth limit that maintain separate buffer limits.

unsafe {
    wasm_api::blueprint::blueprint_call(
        package_address.as_bytes().as_ptr(),
        package_address.as_bytes().len(),
        blueprint_name.as_ptr(),
        blueprint_name.len(),
        function_name.as_ptr(),
        function_name.len(),
        args.as_ptr(),
        args.len(),
    );
}

The attack works by allocating the maximum available memory in the VM, storing this memory in a key-value store entry, creating duplicates through repeated reads while avoiding buffer cleanup, and recursively spawning new call frames to multiply the effect.

Impact

The vulnerability allows a malicious transaction to do the following:

  • Consume approximately 480MB of validator memory (8 call frames x 32 buffer limit x 2MB transaction substate limit), which is a ~250x amplification of the intended memory limits per transaction

  • Cause significant performance degradation with execution times of 5--10 seconds per transaction

  • Create physical memory pressure on validator nodes through repeated physical page allocations and deallocations

  • Potentially lead to out-of-memory conditions in validator processes if multiple transactions are executed concurrently

Recommendations

We recommend two potential approaches to resolve this issue:

  1. Buffer slot--limit reduction

    The current limit of 32 buffer slots appears unnecessary as the Buffer type is only used by the private API. Most WASM APIs return only one buffer, and the application layer forces it to be consumed and cleaned up before returning. Consider reducing the buffer limit to two slots: one slot for general operations and one additional slot to support potential callback scenarios. This would reduce the maximum memory amplification from ~250x to ~16x.

  2. Alternative memory management

    Consider exporting malloc/free from the guest environment, which would enable proper cost tracking in all scenarios. Memory allocations would be tracked and limited appropriately through the cost-unit system, and it would not require any copies in the host state.

The first approach (reducing buffer slots) is likely the simplest and most effective solution, as there are no known legitimate use cases requiring multiple buffer slots.

Remediation

This issue has been acknowledged by Radix Publishing Limited, and a fix was implemented in commit cd5999b2.

Zellic © 2025Back to top ↑