blob: 68b01fb3909cd671e89af3d7f68c2dca753fac20 [file] [log] [blame]
// Copyright (C) 2017-2018 Baidu, Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Baidu, Inc., nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
extern crate sgx_types;
extern crate sgx_urts;
extern crate dirs;
extern crate wabt;
extern crate wasmi;
use sgx_types::*;
use sgx_urts::SgxEnclave;
use std::io::{Read, Write};
use std::{fs, path};
use wasmi::{RuntimeValue, Error as InterpreterError};
use wabt::script::{Action, Command, CommandKind, ScriptParser, Value};
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
static ENCLAVE_FILE: &'static str = "enclave.signed.so";
static ENCLAVE_TOKEN: &'static str = "enclave.token";
static MAXOUTPUT:usize = 4096;
extern {
fn sgxwasm_init(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t ;
fn sgxwasm_run_action(eid: sgx_enclave_id_t, retval: *mut sgx_status_t,
req_bin : *const u8, req_len: usize,
result_bin : *mut u8,
result_max_len : usize ) -> sgx_status_t;
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SgxWasmAction {
Invoke {
module: Option<String>,
field: String,
args: Vec<BoundaryValue>
},
Get {
module: Option<String>,
field: String,
},
LoadModule {
name: Option<String>,
module: Vec<u8>,
},
TryLoad {
module: Vec<u8>,
},
Register {
name: Option<String>,
as_name: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub enum BoundaryValue {
I32(i32),
I64(i64),
F32(u32),
F64(u64),
}
fn wabt_runtime_value_to_boundary_value(wabt_rv : &wabt::script::Value) -> BoundaryValue {
match wabt_rv {
&wabt::script::Value::I32(wabt_rv) => BoundaryValue::I32(wabt_rv),
&wabt::script::Value::I64(wabt_rv) => BoundaryValue::I64(wabt_rv),
&wabt::script::Value::F32(wabt_rv) => BoundaryValue::F32(wabt_rv.to_bits()),
&wabt::script::Value::F64(wabt_rv) => BoundaryValue::F64(wabt_rv.to_bits()),
}
}
#[allow(dead_code)]
fn runtime_value_to_boundary_value(rv: RuntimeValue) -> BoundaryValue {
match rv {
RuntimeValue::I32(rv) => BoundaryValue::I32(rv),
RuntimeValue::I64(rv) => BoundaryValue::I64(rv),
RuntimeValue::F32(rv) => BoundaryValue::F32(rv.to_bits()),
RuntimeValue::F64(rv) => BoundaryValue::F64(rv.to_bits()),
}
}
fn boundary_value_to_runtime_value(rv: BoundaryValue) -> RuntimeValue {
match rv {
BoundaryValue::I32(bv) => RuntimeValue::I32(bv),
BoundaryValue::I64(bv) => RuntimeValue::I64(bv),
BoundaryValue::F32(bv) => RuntimeValue::F32(f32::from_bits(bv)),
BoundaryValue::F64(bv) => RuntimeValue::F64(f64::from_bits(bv)),
}
}
pub fn answer_convert(res : Result<Option<BoundaryValue>, InterpreterError>)
-> Result<Option<RuntimeValue>, InterpreterError>
{
match res {
Ok(None) => Ok(None),
Ok(Some(rv)) => Ok(Some(boundary_value_to_runtime_value(rv))),
Err(x) => Err(x),
}
}
fn spec_to_runtime_value(value: Value) -> RuntimeValue {
match value {
Value::I32(v) => RuntimeValue::I32(v),
Value::I64(v) => RuntimeValue::I64(v),
Value::F32(v) => RuntimeValue::F32(v),
Value::F64(v) => RuntimeValue::F64(v),
}
}
fn init_enclave() -> SgxResult<SgxEnclave> {
let mut launch_token: sgx_launch_token_t = [0; 1024];
let mut launch_token_updated: i32 = 0;
// Step 1: try to retrieve the launch token saved by last transaction
// if there is no token, then create a new one.
//
// try to get the token saved in $HOME */
let mut home_dir = path::PathBuf::new();
let use_token = match dirs::home_dir() {
Some(path) => {
println!("[+] Home dir is {}", path.display());
home_dir = path;
true
},
None => {
println!("[-] Cannot get home dir");
false
}
};
let token_file: path::PathBuf = home_dir.join(ENCLAVE_TOKEN);;
if use_token == true {
match fs::File::open(&token_file) {
Err(_) => {
println!("[-] Open token file {} error! Will create one.", token_file.as_path().to_str().unwrap());
},
Ok(mut f) => {
println!("[+] Open token file success! ");
match f.read(&mut launch_token) {
Ok(1024) => {
println!("[+] Token file valid!");
},
_ => println!("[+] Token file invalid, will create new token file"),
}
}
}
}
// Step 2: call sgx_create_enclave to initialize an enclave instance
// Debug Support: set 2nd parameter to 1
let debug = 1;
let mut misc_attr = sgx_misc_attribute_t {secs_attr: sgx_attributes_t { flags:0, xfrm:0}, misc_select:0};
let enclave = try!(SgxEnclave::create(ENCLAVE_FILE,
debug,
&mut launch_token,
&mut launch_token_updated,
&mut misc_attr));
// Step 3: save the launch token if it is updated
if use_token == true && launch_token_updated != 0 {
// reopen the file with write capablity
match fs::File::create(&token_file) {
Ok(mut f) => {
match f.write_all(&launch_token) {
Ok(()) => println!("[+] Saved updated launch token!"),
Err(_) => println!("[-] Failed to save updated launch token!"),
}
},
Err(_) => {
println!("[-] Failed to save updated enclave token, but doesn't matter");
},
}
}
Ok(enclave)
}
fn sgx_enclave_wasm_init(enclave : &SgxEnclave) -> Result<(),String> {
let mut retval:sgx_status_t = sgx_status_t::SGX_SUCCESS;
let result = unsafe {
sgxwasm_init(enclave.geteid(),
&mut retval)
};
match result {
sgx_status_t::SGX_SUCCESS => {},
_ => {
println!("[-] ECALL Enclave Failed {}!", result.as_str());
panic!("sgx_enclave_wasm_init's ECALL returned unknown error!");
}
}
match retval {
sgx_status_t::SGX_SUCCESS => {},
_ => {
println!("[-] ECALL Enclave Function return fail: {}!", retval.as_str());
return Err(format!("ECALL func return error: {}", retval.as_str()));
}
}
Ok(())
}
fn sgx_enclave_wasm_invoke(req_str : String,
result_max_len : usize,
enclave : &SgxEnclave) -> (Result<Option<BoundaryValue>, InterpreterError>, sgx_status_t) {
let enclave_id = enclave.geteid();
let mut ret_val = sgx_status_t::SGX_SUCCESS;
let req_bin = req_str.as_ptr() as * const u8;
let req_len = req_str.len();
let mut result_vec:Vec<u8> = vec![0; result_max_len];
let result_slice = &mut result_vec[..];
let sgx_ret = unsafe{sgxwasm_run_action(enclave_id,
&mut ret_val,
req_bin,
req_len,
result_slice.as_mut_ptr(),
result_max_len)};
match sgx_ret {
// sgx_ret falls in range of Intel's Error code set
sgx_status_t::SGX_SUCCESS => {},
_ => {
println!("[-] ECALL Enclave Failed {}!", sgx_ret.as_str());
panic!("sgx_enclave_wasm_load_invoke's ECALL returned unknown error!");
}
}
// We need to trim all trailing '\0's before conver to string
let mut result_vec:Vec<u8> = result_slice.to_vec();
result_vec.retain(|x| *x != 0x00u8);
//let result_str : String;
let result:Result<Option<BoundaryValue>, InterpreterError>;
// Now result_vec only includes essential chars
if result_vec.len() == 0 {
result = Ok(None);
}
else{
let raw_result_str = String::from_utf8(result_vec).unwrap();
result = serde_json::from_str(&raw_result_str).unwrap();
}
match ret_val {
// ret_val falls in range of [SGX_SUCCESS + SGX_ERROR_WASM_*]
sgx_status_t::SGX_SUCCESS => {},
_ => {
// In this case, the returned buffer is useful
return (result, ret_val);
}
}
// ret_val should be SGX_SUCCESS here
(result, ret_val)
}
fn sgx_enclave_wasm_load_module(module : Vec<u8>,
name : &Option<String>,
enclave : &SgxEnclave)
-> Result<(), String> {
// Init a SgxWasmAction::LoadModule struct and send it to enclave
let req = SgxWasmAction::LoadModule {
name : name.as_ref().map(|x| x.clone()),
module : module,
};
match sgx_enclave_wasm_invoke(serde_json::to_string(&req).unwrap(),
MAXOUTPUT,
enclave) {
(_, sgx_status_t::SGX_SUCCESS) => {
Ok(())
},
(Err(x), sgx_status_t::SGX_ERROR_WASM_LOAD_MODULE_ERROR) => {
Err(x.to_string())
},
(_, _) => {
println!("sgx_enclave_wasm_load_module should not arrive here!");
panic!("sgx_enclave_wasm_load_module returned unknown error!");
},
}
}
fn sgx_enclave_wasm_run_action(action : &Action, enclave : &SgxEnclave) -> Result<Option<RuntimeValue>, InterpreterError> {
match action {
&Action::Invoke {
ref module,
ref field,
ref args,
} => {
// Deal with Invoke
// Make a SgxWasmAction::Invoke structure and send it to sgx_enclave_wasm_invoke
let req = SgxWasmAction::Invoke {
module : module.as_ref().map(|x| x.clone()),
field : field.clone(),
args : args.into_iter()
.map(wabt_runtime_value_to_boundary_value)
.collect()
};
let result = sgx_enclave_wasm_invoke(serde_json::to_string(&req).unwrap(),
MAXOUTPUT,
enclave);
match result {
(result, sgx_status_t::SGX_SUCCESS) => {
let result_obj : Result<Option<RuntimeValue>, InterpreterError> = answer_convert(result);
result_obj
},
(result, sgx_status_t::SGX_ERROR_WASM_INTERPRETER_ERROR) => {
let result_obj : Result<Option<RuntimeValue>, InterpreterError> = answer_convert(result);
result_obj
},
(_, _) => {
println!("sgx_enclave_wasm_run_action::Invoke returned unknown error!");
panic!("sgx_enclave_wasm_run_action::Invoke returned unknown error!");
},
}
},
&Action::Get {
ref module,
ref field,
..
} => {
// Deal with Get
// Make a SgxWasmAction::Get structure and send it to sgx_enclave_wasm_invoke
let req = SgxWasmAction::Get {
module : module.as_ref().map(|x| x.clone()),
field : field.clone(),
};
let result = sgx_enclave_wasm_invoke(serde_json::to_string(&req).unwrap(),
MAXOUTPUT,
enclave);
match result {
(result, sgx_status_t::SGX_SUCCESS) => {
let result_obj : Result<Option<RuntimeValue>, InterpreterError> = answer_convert(result);
result_obj
},
(result, sgx_status_t::SGX_ERROR_WASM_INTERPRETER_ERROR) => {
let result_obj : Result<Option<RuntimeValue>, InterpreterError> = answer_convert(result);
result_obj
},
(_, _) => { println!("sgx_enclave_wasm_run_action::Get returned unknown error!");
panic!("sgx_enclave_wasm_run_action::Get returned unknown error!");
}
}
},
}
}
// Malform
fn sgx_enclave_wasm_try_load(module : &[u8], enclave : &SgxEnclave) -> Result<(), InterpreterError> {
// Make a SgxWasmAction::TryLoad structure and send it to sgx_enclave_wasm_invoke
let req = SgxWasmAction::TryLoad {
module : module.to_vec(),
};
let result = sgx_enclave_wasm_invoke(serde_json::to_string(&req).unwrap(),
MAXOUTPUT,
enclave);
match result {
(_, sgx_status_t::SGX_SUCCESS) => {
Ok(())
},
(Err(x), sgx_status_t::SGX_ERROR_WASM_TRY_LOAD_ERROR) => {
Err(InterpreterError::Global(x.to_string()))
},
(_, _) => {
println!("sgx_enclave_wasm_try_load returned unknown error!");
panic!("sgx_enclave_wasm_try_load returned unknown error!");
}
}
}
// Register
fn sgx_enclave_wasm_register(name : Option<String>,
as_name : String,
enclave : &SgxEnclave) -> Result<(), InterpreterError> {
// Make a SgxWasmAction::Register structure and send it to sgx_enclave_wasm_invoke
let req = SgxWasmAction::Register{
name : name,
as_name : as_name,
};
let result = sgx_enclave_wasm_invoke(serde_json::to_string(&req).unwrap(),
MAXOUTPUT,
enclave);
match result {
(_, sgx_status_t::SGX_SUCCESS) => {
Ok(())
},
(Err(x), sgx_status_t::SGX_ERROR_WASM_REGISTER_ERROR) => {
Err(InterpreterError::Global(x.to_string()))
},
(_, _) => {
println!("sgx_enclave_wasm_register returned unknown error!");
panic!("sgx_enclave_wasm_register returned unknown error!");
}
}
}
fn wasm_main_loop(wast_file : &str, enclave : &SgxEnclave) -> Result<(), String> {
let mut parser = ScriptParser::from_file(wast_file).unwrap();
sgx_enclave_wasm_init(enclave)?;
while let Some(Command{kind,line}) =
match parser.next() {
Ok(x) => x,
_ => { return Err("Error parsing test input".to_string()); }
}
{
println!("Line : {}", line);
match kind {
CommandKind::Module { name, module, .. } => {
sgx_enclave_wasm_load_module (module.into_vec().unwrap(), &name, enclave)?;
println!("load module - success at line {}", line)
},
CommandKind::AssertReturn { action, expected } => {
let result:Result<Option<RuntimeValue>, InterpreterError> = sgx_enclave_wasm_run_action(&action, enclave);
match result {
Ok(result) => {
let spec_expected = expected.iter()
.cloned()
.map(spec_to_runtime_value)
.collect::<Vec<_>>();
let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>();
for (actual_result, spec_expected) in actual_result.iter().zip(spec_expected.iter()) {
assert_eq!(actual_result.value_type(), spec_expected.value_type());
// f32::NAN != f32::NAN
match spec_expected {
&RuntimeValue::F32(val) if val.is_nan() => match actual_result {
&RuntimeValue::F32(val) => assert!(val.is_nan()),
_ => unreachable!(), // checked above that types are same
},
&RuntimeValue::F64(val) if val.is_nan() => match actual_result {
&RuntimeValue::F64(val) => assert!(val.is_nan()),
_ => unreachable!(), // checked above that types are same
},
spec_expected @ _ => assert_eq!(actual_result, spec_expected),
}
}
println!("assert_return at line {} - success", line);
},
Err(e) => {
panic!("Expected action to return value, got error: {:?}", e);
}
}
},
CommandKind::AssertReturnCanonicalNan { action }
| CommandKind::AssertReturnArithmeticNan { action } => {
let result:Result<Option<RuntimeValue>, InterpreterError> = sgx_enclave_wasm_run_action(&action, enclave);
match result {
Ok(result) => {
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
match actual_result {
RuntimeValue::F32(val) => if !val.is_nan() {
panic!("Expected nan value, got {:?}", val)
},
RuntimeValue::F64(val) => if !val.is_nan() {
panic!("Expected nan value, got {:?}", val)
},
val @ _ => {
panic!("Expected action to return float value, got {:?}", val)
}
}
}
println!("assert_return_nan at line {} - success", line);
}
Err(e) => {
panic!("Expected action to return value, got error: {:?}", e);
}
}
},
CommandKind::AssertExhaustion { action, .. } => {
let result:Result<Option<RuntimeValue>, InterpreterError> = sgx_enclave_wasm_run_action(&action, enclave);
match result {
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e),
}
},
CommandKind::AssertTrap { action, .. } => {
println!("Enter AssertTrap!");
let result:Result<Option<RuntimeValue>, InterpreterError> = sgx_enclave_wasm_run_action(&action, enclave);
match result {
Ok(result) => {
panic!("Expected action to result in a trap, got result: {:?}", result);
},
Err(e) => {
println!("assert_trap at line {} - success ({:?})", line, e);
},
}
},
CommandKind::AssertInvalid { module, .. }
| CommandKind::AssertMalformed { module, .. }
| CommandKind::AssertUnlinkable { module, .. } => {
// Malformed
let module_load = sgx_enclave_wasm_try_load(&module.into_vec().unwrap(), enclave);
match module_load {
Ok(_) => panic!("Expected invalid module definition, got some module!"),
Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e),
}
},
CommandKind::AssertUninstantiable { module, .. } => {
let module_load = sgx_enclave_wasm_try_load(&module.into_vec().unwrap(), enclave);
match module_load {
Ok(_) => panic!("Expected error running start function at line {}", line),
Err(e) => println!("assert_uninstantiable - success ({:?})", e),
}
},
CommandKind::Register { name, as_name, .. } => {
let result = sgx_enclave_wasm_register(name, as_name, enclave);
match result {
Ok(_) => {println!("register - success at line {}", line)},
Err(e) => panic!("No such module, at line {} - ({:?})", e, line),
}
},
CommandKind::PerformAction(action) => {
let result:Result<Option<RuntimeValue>, InterpreterError> = sgx_enclave_wasm_run_action(&action, enclave);
match result {
Ok(_) => {println!("invoke - success at line {}", line)},
Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e),
}
},
}
}
println!("[+] all tests passed!");
Ok(())
}
fn run_a_wast(enclave : &SgxEnclave,
wast_file : &str) -> Result<(), String> {
// Step 1: Init the sgxwasm spec driver engine
sgx_enclave_wasm_init(enclave)?;
// Step 2: Load the wast file and run
wasm_main_loop(wast_file, enclave)?;
Ok(())
}
fn main() {
let enclave = match init_enclave() {
Ok(r) => {
println!("[+] Init Enclave Successful {}!", r.geteid());
r
},
Err(x) => {
println!("[-] Init Enclave Failed {}!", x.as_str());
return;
},
};
let wast_list = vec![
"../test_input/break-drop.wast",
"../test_input/const.wast",
"../test_input/nop.wast",
"../test_input/get_local.wast",
"../test_input/elem.wast",
"../test_input/br_table.wast",
"../test_input/typecheck.wast",
"../test_input/exports.wast",
"../test_input/block.wast",
"../test_input/utf8-custom-section-id.wast",
"../test_input/linking.wast",
"../test_input/i64.wast",
"../test_input/traps.wast",
"../test_input/f64_cmp.wast",
"../test_input/set_local.wast",
"../test_input/tee_local.wast",
"../test_input/type.wast",
"../test_input/left-to-right.wast",
"../test_input/memory_trap.wast",
"../test_input/br_if.wast",
"../test_input/call_indirect.wast",
"../test_input/int_exprs.wast",
"../test_input/float_exprs.wast",
"../test_input/unwind.wast",
"../test_input/start.wast",
"../test_input/stack.wast",
"../test_input/f64_bitwise.wast",
"../test_input/memory.wast",
"../test_input/int_literals.wast",
"../test_input/align.wast",
"../test_input/utf8-invalid-encoding.wast",
"../test_input/utf8-import-field.wast",
"../test_input/func_ptrs.wast",
"../test_input/imports.wast",
"../test_input/float_misc.wast",
"../test_input/memory_redundancy.wast",
"../test_input/f32_cmp.wast",
"../test_input/address.wast",
"../test_input/custom_section.wast",
"../test_input/forward.wast",
"../test_input/loop.wast",
"../test_input/f32_bitwise.wast",
"../test_input/br.wast",
"../test_input/labels.wast",
"../test_input/utf8-import-module.wast",
"../test_input/return.wast",
"../test_input/store_retval.wast",
"../test_input/comments.wast",
"../test_input/resizing.wast",
"../test_input/i32.wast",
"../test_input/float_memory.wast",
"../test_input/f32.wast",
"../test_input/unreachable.wast",
"../test_input/token.wast",
"../test_input/unreached-invalid.wast",
"../test_input/binary.wast",
"../test_input/select.wast",
"../test_input/f64.wast",
"../test_input/if.wast",
"../test_input/func.wast",
"../test_input/call.wast",
"../test_input/fac.wast",
"../test_input/switch.wast",
"../test_input/names.wast",
"../test_input/endianness.wast",
"../test_input/conversions.wast",
"../test_input/inline-module.wast",
"../test_input/float_literals.wast",
"../test_input/globals.wast",
"../test_input/skip-stack-guard-page.wast",
];
for wfile in wast_list {
println!("======================= testing {} =====================", wfile);
run_a_wast(&enclave, wfile).unwrap();
}
enclave.destroy();
println!("[+] run_wasm success...");
return;
}