wasmedge-rust-sdk icon indicating copy to clipboard operation
wasmedge-rust-sdk copied to clipboard

bug: potential data disclosure is observed in WasmEdge Rust SDK

Open candymate opened this issue 10 months ago • 4 comments

Quick notice

The WasmEdge team confirmed that this can be public, so I'm uploading this as a public issue.

Description

There is a potential data leakage between WasmValue and WasmEdge_Value which possibly disclose address base of some mmap'ed map. Since the data is represented in i128 in WasmEdge_Value, it may contain uninitialized values on the high bits of the value. For example, if a WASM program that returns a single f32 constant of 1.0 (0x3f800000) is executed using the Rust SDK, data disclosures appear as [WasmValue { ctx: WasmEdge_Value { Value: 93910525280256, Type: 125 }, ty: F32 }], which the hex value is 0x5569_3f800000. (i.e., 0x5569 is leaked)

pub struct WasmEdge_Value {
  pub Value: uint128_t,
  pub Type: WasmEdge_ValType,
}
(module
  (type (;0;) (func (result f32)))
  (func (;0;) (type 0) (result f32)
    f32.const 0x1p+0 (;=1;))
  (export "main" (func 0)))

It appears the value changes each time the function is executed. My guess is that the disclosed value points to a mmap'ed memory region used in the execution of the program, possibly the pointer to the region. If this is true, the attackers might be able to use the disclosed value to find the base address of the region, possibly elaborate it into an arbitrary code execution if they find and combine an arbitrary write vulnerability.

Current State

The data disclosure happens across all optimization levels (O0~Oz). The disclosed data changes every time the program is executed. One example would be the following: (execution with the wasm program above)

[WasmValue { ctx: WasmEdge_Value { Value: 93910525280256, Type: 125 }, ty: F32 }]

The data is also disclosed in non-AOT mode. However, the disclosed data is always 0x10000000.

Expected State

There shouldn't be any disclosed data. The value should be clearly 0x3f800000.

Versions

  • WasmEdge 0.13.5
  • wasmedge-sdk (rust) 0.13.2, with feature "aot"

PoC Code

Run the following Rust code with the wasm program above:

use wasmedge_sdk::config::CommonConfigOptions;
use wasmedge_sdk::config::CompilerConfigOptions;
use wasmedge_sdk::config::ConfigBuilder;
use wasmedge_sdk::types::Val;
use wasmedge_sdk::Compiler;
use wasmedge_sdk::CompilerOptimizationLevel;
use wasmedge_sdk::CompilerOutputFormat;
use wasmedge_sdk::LogManager;
use wasmedge_sdk::Module;
use wasmedge_sdk::VmBuilder;
use anyhow::{bail, Error};

fn main() -> Result<(), Error> {
    LogManager::log_off();
    
    let config = ConfigBuilder::new(CommonConfigOptions::default())
        .with_compiler_config(
            CompilerConfigOptions::default()
                .optimization_level(CompilerOptimizationLevel::O0) // change here for optimization level
                .out_format(CompilerOutputFormat::Native))
        .build()?;
    let compiler = Compiler::new(Some(&config))?;
    let aot_file_path = compiler.compile_from_file("test.wasm", "aot", ".")?;
    let module = Module::from_file(Some(&config), aot_file_path)?;
    let vm = VmBuilder::new().with_config(config).build()?.register_module(None, module)?;

    let instance = vm.active_module()?;
    let main = match instance.func_names() {
        Some(x) => instance.func(x[0].clone())?,
        None => bail!("There is no exported function"),
    };
    
    let params: [Val; 0] = Default::default();
    match main.run(vm.executor(), params.into_iter().map(|x| x.into())) {
        Ok(results) => println!("{:?}", results),
        Err(_e) => println!("Error"),
    }

    Ok(())
}

candymate avatar Apr 19 '24 08:04 candymate