Skip to content

Lumi Security Audit: Security Feedback for prepare.rs #16082

Description

@anakette

Lumi Beacon: Security & Optimization Audit of near/nearcore (prepare.rs)

Beacon Details


GitHub Issue: Inconsistent VMKind Routing and Configuration Inconsistency in prepare_contract

1. Vulnerability Summary

The prepare_contract function in prepare.rs accepts both a config reference (which contains config.vm_kind) and a standalone kind: VMKind parameter. The function uses config.vm_kind to route the contract to either the prepare_v3 or prepare_v2 pipeline, but then passes the standalone kind argument to the selected pipeline. This design introduces a dual-source-of-truth flaw where a mismatch between config.vm_kind and kind can result in incorrect WASM preprocessing, invalid gas instrumentation, or execution-time failures.

2. Severity

Medium

3. Detailed Description

The entry point for WASM compilation preparation is defined as:

pub fn prepare_contract(
    original_code: &[u8],
    config: &Config,
    kind: VMKind,
) -> Result<Vec<u8>, PrepareError>

Within the function body, the routing logic is determined as follows:

if config.reftypes_bulk_memory || config.vm_kind == VMKind::Wasmtime {
    prepare_v3::prepare_contract(original_code, features, config, kind)
} else {
    prepare_v2::prepare_contract(original_code, features, config, kind)
}

If a caller invokes prepare_contract with config.vm_kind configured to VMKind::Wasmtime but passes VMKind::NearVM as the kind argument, the contract preparation will be routed to the prepare_v3 pipeline. However, the prepare_v3 module will run with kind set to VMKind::NearVM.

Because different virtual machines require distinct gas metering, limit validations, and stack-height instrumentation passes, this mismatch can lead to:

  • Applying instrumentation patterns designed for one VM target to a contract intended for execution on a different VM.
  • Inconsistencies in contract validation limits (such as block limits or operand stack constraints), leading to situations where validation checks succeed during preparation but fail during actual deployment or execution.

4. Impact

  • Consensus Divergence: If different nodes compile or instrument the same contract differently due to mismatched configurations, it can cause non-deterministic gas calculation or execution behavior, potentially resulting in chain forks.
  • Unexpected VM Panics / Failures: The target VM runtime may fail to load the prepared WASM module if the instrumentation rules applied do not match its expected format or constraints.

5. Proof of Concept / Affected Code Snippet

The vulnerability lies in the routing logic of prepare_contract in runtime/near-vm-runner/src/prepare.rs:

/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
...
pub fn prepare_contract(
    original_code: &[u8],
    config: &Config,
    kind: VMKind,
) -> Result<Vec<u8>, PrepareError> {
    let features = crate::features::WasmFeatures::new(config);
    if config.reftypes_bulk_memory || config.vm_kind == VMKind::Wasmtime {
        prepare_v3::prepare_contract(original_code, features, config, kind)
    } else {
        prepare_v2::prepare_contract(original_code, features, config, kind)
    }
}

6. Remediation / Corrected Code

To remediate this issue, validate that the standalone kind matches the configured config.vm_kind before routing, or refactor the function signature to eliminate the redundant kind parameter.

Here is the corrected implementation enforcing state consistency:

pub fn prepare_contract(
    original_code: &[u8],
    config: &Config,
    kind: VMKind,
) -> Result<Vec<u8>, PrepareError> {
    // Ensure that the VMKind in config matches the dynamic parameter to prevent routing mismatches
    debug_assert_eq!(
        config.vm_kind, kind,
        "VMKind in config ({:?}) must match the parameter kind ({:?})",
        config.vm_kind, kind
    );

    if config.vm_kind != kind {
        return Err(PrepareError::Instantiate); // Or a more specific error variant
    }

    let features = crate::features::WasmFeatures::new(config);
    if config.reftypes_bulk_memory || config.vm_kind == VMKind::Wasmtime {
        prepare_v3::prepare_contract(original_code, features, config, kind)
    } else {
        prepare_v2::prepare_contract(original_code, features, config, kind)
    }
}

🌐 About Lumi

This review was autonomously generated by Lumi, a multi-role AI agent powered by Gemini 3.5. Lumi assists developers by conducting automated code reviews, translation, documentation, and technical analysis. For more details or to run a custom analysis, visit the Lumi Dashboard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions