Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

boxed the wasmer_instance field of Instance to make it safe to reference #306

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ The imports provided to give the contract access to the environment are:
// TODO: use feature switches to enable precompile dependencies in the future,
// so contracts that need less
extern "C" {
fn db_read(key: *const c_void, value: *mut c_void) -> i32;
fn db_read(key: *const c_void) -> i32;
fn db_write(key: *const c_void, value: *mut c_void) -> i32;
fn db_remove(key: *const c_void) -> i32;
fn canonicalize_address(human: *const c_void, canonical: *mut c_void) -> i32;
Expand Down
30 changes: 10 additions & 20 deletions packages/std/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ use crate::traits::{Api, Querier, QuerierResponse, ReadonlyStorage, Storage};
use crate::traits::{Order, KV};
use crate::types::{CanonicalAddr, HumanAddr};

// this is the buffer we pre-allocate in get - we should configure this somehow later
static MAX_READ: usize = 2000;

// this is the maximum allowed size for bech32
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
static ADDR_BUFFER: usize = 90;
Expand All @@ -26,7 +23,7 @@ static QUERY_BUFFER: usize = 4000;
// A complete documentation those functions is available in the VM that provides them:
// https://github.com/confio/cosmwasm/blob/0.7/lib/vm/src/instance.rs#L43
extern "C" {
fn db_read(key: *const c_void, value: *mut c_void) -> i32;
fn db_read(key: *const c_void) -> i32;
fn db_write(key: *const c_void, value: *mut c_void) -> i32;
fn db_remove(key: *const c_void) -> i32;

Expand Down Expand Up @@ -58,24 +55,17 @@ impl ReadonlyStorage for ExternalStorage {
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
let key = build_region(key);
let key_ptr = &*key as *const Region as *const c_void;
let value = alloc(MAX_READ);

let read = unsafe { db_read(key_ptr, value) };
if read == -1000002 {
panic!("Allocated memory too small to hold the database value for the given key. \
If this is causing trouble for you, have a look at https://github.com/confio/cosmwasm/issues/126");
} else if read < 0 {
panic!("An unknown error occurred in the db_read call.")

let read = unsafe { db_read(key_ptr) };
if read < 0 {
panic!("An unknown error occurred in the db_read call");
} else if read == 0 {
return None;
}

match unsafe { consume_region(value) } {
Ok(data) => {
if data.len() == 0 {
None
} else {
Some(data)
}
}
let value_ptr = read as u32;
match unsafe { consume_region(value_ptr as *mut c_void) } {
Ok(data) => Some(data),
// TODO: do we really want to convert errors to None?
Err(_) => None,
}
Expand Down
138 changes: 126 additions & 12 deletions packages/vm/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
use std::collections::HashMap;
use std::ffi::c_void;
use std::mem;
use std::ptr::NonNull;

use wasmer_runtime_core::vm::Ctx;
use snafu::ResultExt;
use wasmer_runtime_core::{
typed_func::{Func, Wasm, WasmTypeList},
vm::Ctx,
};

#[cfg(feature = "iterator")]
use cosmwasm_std::KV;
Expand All @@ -13,9 +18,8 @@ use cosmwasm_std::{
QuerierResponse, QueryRequest, Storage,
};

#[cfg(feature = "iterator")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports under it are needed in your PR, but they are not available without this feature enabled. this caused a compilation fail when not using this feature.

use crate::conversion::to_i32;
use crate::errors::{Error, Result, UninitializedContextData};
use crate::conversion::{to_i32, to_u32};
use crate::errors::{Error, Result, UninitializedContextData, WasmerRuntimeErr};
use crate::memory::{read_region, write_region};
use crate::serde::{from_slice, to_vec};
#[cfg(feature = "iterator")]
Expand Down Expand Up @@ -49,7 +53,7 @@ pub static ERROR_NO_CONTEXT_DATA: i32 = -1_000_501;
static ERROR_DB_UNKNOWN: i32 = -1_000_502;

/// Reads a storage entry from the VM's storage into Wasm memory
pub fn do_read<S: Storage, Q: Querier>(ctx: &Ctx, key_ptr: u32, value_ptr: u32) -> i32 {
pub fn do_read<S: Storage, Q: Querier>(ctx: &Ctx, key_ptr: u32) -> i32 {
let key = match read_region(ctx, key_ptr, MAX_LENGTH_DB_KEY) {
Ok(data) => data,
Err(Error::RegionLengthTooBigErr { .. }) => return ERROR_REGION_READ_LENGTH_TOO_BIG,
Expand All @@ -61,13 +65,36 @@ pub fn do_read<S: Storage, Q: Querier>(ctx: &Ctx, key_ptr: u32, value_ptr: u32)
Err(Error::UninitializedContextData { .. }) => return ERROR_NO_CONTEXT_DATA,
Err(_) => return ERROR_DB_UNKNOWN,
};
match value {
Some(buf) => match write_region(ctx, value_ptr, &buf) {
Ok(()) => SUCCESS,
Err(Error::RegionTooSmallErr { .. }) => ERROR_REGION_WRITE_TOO_SMALL,
Err(_) => ERROR_REGION_WRITE_UNKNOWN,

let data = match value {
Some(d) => d,
None => return SUCCESS,
};

println!("Data len: {}", data.len());

let value_ptr =
match with_func_from_context::<S, Q, u32, u32, _, _>(ctx, "allocate", |allocate_func| {
println!("Found allocate function");
let size = to_u32(data.len())?;
allocate_func.call(size).context(WasmerRuntimeErr {})
}) {
Ok(ptr) => ptr,
Err(_) => {
println!("Error executing allocate");
return -34567890; // error executing allocate
}
};

println!("value_ptr = {}", value_ptr);

match write_region(ctx, value_ptr, &data) {
Ok(()) => match to_i32(value_ptr) {
Ok(iptr) => iptr,
Err(_) => return -98765, // Error converting to i32 range
},
None => SUCCESS,
Err(Error::RegionTooSmallErr { .. }) => ERROR_REGION_WRITE_TOO_SMALL,
Err(_) => ERROR_REGION_WRITE_UNKNOWN,
}
}

Expand Down Expand Up @@ -318,6 +345,7 @@ mod iter_support {
struct ContextData<S: Storage, Q: Querier> {
storage: Option<S>,
querier: Option<Q>,
wasmer_instance: Option<NonNull<wasmer_runtime_core::instance::Instance>>,
#[cfg(feature = "iterator")]
iter: HashMap<u32, Box<dyn Iterator<Item = KV>>>,
}
Expand All @@ -333,6 +361,7 @@ fn create_unmanaged_context_data<S: Storage, Q: Querier>() -> *mut c_void {
let data = ContextData::<S, Q> {
storage: None,
querier: None,
wasmer_instance: None,
#[cfg(feature = "iterator")]
iter: HashMap::new(),
};
Expand All @@ -348,6 +377,17 @@ fn destroy_unmanaged_context_data<S: Storage, Q: Querier>(ptr: *mut c_void) {
}
}

/// Creates a back reference from a contact to its partent instance
pub fn set_wasmer_instance<S: Storage, Q: Querier>(
ctx: &Ctx,
wasmer_instance: NonNull<wasmer_runtime_core::instance::Instance>,
) {
let context_data = ctx.data as *mut ContextData<S, Q>;
unsafe {
(*context_data).wasmer_instance = Some(wasmer_instance);
}
}

unsafe fn get_data<S: Storage, Q: Querier>(ptr: *mut c_void) -> Box<ContextData<S, Q>> {
Box::from_raw(ptr as *mut ContextData<S, Q>)
}
Expand All @@ -363,6 +403,47 @@ fn free_iterator<S: Storage, Q: Querier>(context: &mut ContextData<S, Q>) {
#[cfg(not(feature = "iterator"))]
fn free_iterator<S: Storage, Q: Querier>(_context: &mut ContextData<S, Q>) {}

pub(crate) fn with_func_from_context<S, Q, Args, Rets, Callback, CallbackData>(
ctx: &Ctx,
name: &str,
mut callback: Callback,
) -> Result<CallbackData, Error>
where
S: Storage,
Q: Querier,
Args: WasmTypeList,
Rets: WasmTypeList,
Callback: FnMut(Func<Args, Rets, Wasm>) -> Result<CallbackData, Error>,
{
let context_data = ctx.data as *mut ContextData<S, Q>;
println!("Got context");
match unsafe { (*context_data).wasmer_instance } {
Some(ptr) => {
println!("Got wasmer instance pointer");
let instance = unsafe { ptr.as_ref() };
println!("Got wasmer instance ref");
let _context = instance.context();
println!("Could execute context function on wasmer instance ref");
let func = match instance.func(name) {
Ok(func) => {
println!("Found func");
func
}
Err(e) => {
println!("Error in func(): {:?}", e);
panic!("Error in func(): {:?}", e);
}
}; // .context(ResolveErr {})?;
println!("Executing func ...");
callback(func)
}
None => UninitializedContextData {
kind: "wasmer_instance",
}
.fail(),
}
}

pub(crate) fn with_storage_from_context<S, Q, F, T>(ctx: &Ctx, mut func: F) -> Result<T, Error>
where
S: Storage,
Expand Down Expand Up @@ -451,7 +532,7 @@ mod test {
let import_obj = imports! {
|| { setup_context::<MockStorage, MockQuerier>() },
"env" => {
"db_read" => Func::new(|_a: i32, _b: i32| -> i32 { 0 }),
"db_read" => Func::new(|_a: i32| -> i32 { 0 }),
"db_write" => Func::new(|_a: i32, _b: i32| -> i32 { 0 }),
"db_remove" => Func::new(|_a: i32| -> i32 { 0 }),
"db_scan" => Func::new(|_a: i32, _b: i32, _c: i32| -> i32 { 0 }),
Expand Down Expand Up @@ -497,6 +578,39 @@ mod test {
assert!(endq.is_none());
}

#[test]
fn with_func_from_context_works() {
let mut instance = make_instance();
leave_default_data(&instance);
let instance_ptr = NonNull::from(&mut instance);
set_wasmer_instance::<S, Q>(instance.context(), instance_ptr);

let ctx = instance.context();
let _ptr = with_func_from_context::<S, Q, u32, u32, _, _>(ctx, "allocate", |alloc_func| {
alloc_func.call(10).context(WasmerRuntimeErr {})
})
.expect("with_func_from_context should not fail");
}

#[test]
fn with_func_from_context_fails_for_missing_instance() {
let instance = make_instance();
leave_default_data(&instance);
let ctx = instance.context();

let res = with_func_from_context::<S, Q, u32, u32, _, _>(ctx, "allocate", |alloc_func| {
alloc_func.call(10).expect("error calling allocate");
Ok(())
});
match res {
Ok(_) => panic!("this must not succeed"),
Err(Error::UninitializedContextData { kind, .. }) => {
assert_eq!(kind, "wasmer_instance")
}
Err(e) => panic!("Unexpected error: {}", e),
}
}

#[test]
fn with_storage_set_get() {
// this creates an instance
Expand Down
21 changes: 16 additions & 5 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::marker::PhantomData;
use std::ptr::NonNull;

use snafu::ResultExt;
pub use wasmer_runtime_core::typed_func::Func;
Expand All @@ -14,7 +15,8 @@ use cosmwasm_std::{Api, Extern, Querier, Storage};
use crate::backends::{compile, get_gas, set_gas};
use crate::context::{
do_canonicalize_address, do_humanize_address, do_query_chain, do_read, do_remove, do_write,
move_from_context, move_into_context, setup_context, with_storage_from_context,
move_from_context, move_into_context, set_wasmer_instance, setup_context,
with_storage_from_context,
};
#[cfg(feature = "iterator")]
use crate::context::{do_next, do_scan};
Expand All @@ -23,7 +25,7 @@ use crate::errors::{ResolveErr, Result, WasmerErr, WasmerRuntimeErr};
use crate::memory::{read_region, write_region};

pub struct Instance<S: Storage + 'static, A: Api + 'static, Q: Querier + 'static> {
wasmer_instance: wasmer_runtime_core::instance::Instance,
wasmer_instance: Box<wasmer_runtime_core::instance::Instance>,
pub api: A,
// This does not store data but only fixes type information
type_storage: PhantomData<S>,
Expand Down Expand Up @@ -53,8 +55,8 @@ where
// Returns 0 on success. Returns negative value on error. An incomplete list of error codes is:
// value region too small: -1000002
// Ownership of both input and output pointer is not transferred to the host.
"db_read" => Func::new(move |ctx: &mut Ctx, key_ptr: u32, value_ptr: u32| -> i32 {
do_read::<S, Q>(ctx, key_ptr, value_ptr)
"db_read" => Func::new(move |ctx: &mut Ctx, key_ptr: u32| -> i32 {
do_read::<S, Q>(ctx, key_ptr)
}),
// Writes the given value into the database entry at the given key.
// Ownership of both input and output pointer is not transferred to the host.
Expand Down Expand Up @@ -119,6 +121,15 @@ where
gas_limit: u64,
) -> Self {
set_gas(&mut wasmer_instance, gas_limit);

// The pointer shenanigans below are sound because:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a safe way to get a reference, but not tested with actually calling it.

Copy link
Contributor Author

@reuvenpo reuvenpo Apr 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true. still requires work, of course

// 1. Boxing the wasmer instance gives it a constant address,
// 2. We never move out of this Box before we finish using this `Instance`
// 3. We provide the context a mut pointer to the wasmer context,
// and we only use it when we actually have unique access to it.
let mut wasmer_instance = Box::new(wasmer_instance);
let instance_ptr = NonNull::from(&mut *wasmer_instance);
set_wasmer_instance::<S, Q>(wasmer_instance.context(), instance_ptr);
move_from_context(wasmer_instance.context(), deps.storage, deps.querier);
Instance {
wasmer_instance,
Expand All @@ -142,7 +153,7 @@ where
} else {
None
};
(instance.wasmer_instance, ext)
(*instance.wasmer_instance, ext)
}

/// Returns the currently remaining gas
Expand Down