diff --git a/soroban-env-host/src/auth.rs b/soroban-env-host/src/auth.rs index dbf1f96f3..26aba98e0 100644 --- a/soroban-env-host/src/auth.rs +++ b/soroban-env-host/src/auth.rs @@ -943,7 +943,7 @@ impl AuthorizationManager { // This should be called for every `Host` `push_frame`. pub(crate) fn push_frame(&self, host: &Host, frame: &Frame) -> Result<(), HostError> { let (contract_id, function_name) = match frame { - Frame::ContractVM(vm, fn_name, ..) => { + Frame::ContractVM { vm, fn_name, .. } => { (vm.contract_id.metered_clone(host.budget_ref())?, *fn_name) } // Skip the top-level host function stack frames as they don't diff --git a/soroban-env-host/src/events/diagnostic.rs b/soroban-env-host/src/events/diagnostic.rs index 032a839a4..5866d3a78 100644 --- a/soroban-env-host/src/events/diagnostic.rs +++ b/soroban-env-host/src/events/diagnostic.rs @@ -64,7 +64,7 @@ impl Host { // Will not return error if frame is missing pub(crate) fn get_current_contract_id_unmetered(&self) -> Result, HostError> { self.with_current_frame_opt(|frame| match frame { - Some(Frame::ContractVM(vm, ..)) => Ok(Some(vm.contract_id.clone())), + Some(Frame::ContractVM { vm, .. }) => Ok(Some(vm.contract_id.clone())), Some(Frame::HostFunction(_)) => Ok(None), Some(Frame::Token(id, ..)) => Ok(Some(id.clone())), #[cfg(any(test, feature = "testutils"))] diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index b3eaf0e46..1d7e5df8c 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -1140,7 +1140,7 @@ impl VmCallerEnv for Host { &vm, pos, vals.as_mut_slice(), - |buf| Val::from_payload(u64::from_le_bytes(*buf)), + |buf| self.relative_to_absolute(Val::from_payload(u64::from_le_bytes(*buf))), )?; self.log_diagnostics(&msg, &vals) @@ -1679,7 +1679,7 @@ impl VmCallerEnv for Host { &vm, vals_pos, vals.as_mut_slice(), - |buf| Val::from_payload(u64::from_le_bytes(*buf)), + |buf| self.relative_to_absolute(Val::from_payload(u64::from_le_bytes(*buf))), )?; for v in vals.iter() { self.check_val_integrity(*v)?; @@ -1740,7 +1740,11 @@ impl VmCallerEnv for Host { &vm, vals_pos.into(), mapobj.map.as_slice(), - |pair| u64::to_le_bytes(pair.1.get_payload()), + |pair| { + Ok(u64::to_le_bytes( + self.absolute_to_relative(pair.1)?.get_payload(), + )) + }, )?; Ok(()) })?; @@ -1969,7 +1973,7 @@ impl VmCallerEnv for Host { &vm, pos, vals.as_mut_slice(), - |buf| Val::from_payload(u64::from_le_bytes(*buf)), + |buf| self.relative_to_absolute(Val::from_payload(u64::from_le_bytes(*buf))), )?; for v in vals.iter() { self.check_val_integrity(*v)?; @@ -1991,7 +1995,11 @@ impl VmCallerEnv for Host { &vm, vals_pos.into(), vecobj.as_slice(), - |x| u64::to_le_bytes(x.get_payload()), + |x| { + Ok(u64::to_le_bytes( + self.absolute_to_relative(*x)?.get_payload(), + )) + }, ) })?; Ok(Val::VOID) @@ -2848,8 +2856,8 @@ impl VmCallerEnv for Host { let mut outer = Vec::with_capacity(contexts.len()); for context in contexts.iter() { let vals = match &context.frame { - Frame::ContractVM(vm, function, ..) => { - get_host_val_tuple(&vm.contract_id, &function)? + Frame::ContractVM { vm, fn_name, .. } => { + get_host_val_tuple(&vm.contract_id, fn_name)? } Frame::HostFunction(_) => continue, Frame::Token(id, function, ..) => get_host_val_tuple(id, function)?, @@ -2907,7 +2915,7 @@ impl VmCallerEnv for Host { ) -> Result { let args = self.with_current_frame(|f| { let args = match f { - Frame::ContractVM(_, _, args, _) => args, + Frame::ContractVM { args, .. } => args, Frame::HostFunction(_) => { return Err(self.err( ScErrorType::Context, diff --git a/soroban-env-host/src/host/frame.rs b/soroban-env-host/src/host/frame.rs index 81ad16c87..7a865bd23 100644 --- a/soroban-env-host/src/host/frame.rs +++ b/soroban-env-host/src/host/frame.rs @@ -9,7 +9,7 @@ use crate::{ err, storage::{InstanceStorageMap, StorageMap}, xdr::{ContractCostType, ContractExecutable, Hash, HostFunction, HostFunctionType, ScVal}, - Error, Host, HostError, Symbol, SymbolStr, TryFromVal, TryIntoVal, Val, + Error, Host, HostError, Object, Symbol, SymbolStr, TryFromVal, TryIntoVal, Val, }; #[cfg(any(test, feature = "testutils"))] @@ -104,7 +104,13 @@ pub(crate) struct Context { /// commit or roll back that state when it pops the stack. #[derive(Clone)] pub(crate) enum Frame { - ContractVM(Rc, Symbol, Vec, ScContractInstance), + ContractVM { + vm: Rc, + fn_name: Symbol, + args: Vec, + instance: ScContractInstance, + relative_objects: Vec, + }, HostFunction(HostFunctionType), Token(Hash, Symbol, Vec, ScContractInstance), #[cfg(any(test, feature = "testutils"))] @@ -246,6 +252,30 @@ impl Host { } } + pub(crate) fn with_current_frame_relative_object_table( + &self, + f: F, + ) -> Result + where + F: FnOnce(&mut Vec) -> Result, + { + self.with_current_context_mut(|ctx| { + if let Frame::ContractVM { + relative_objects, .. + } = &mut ctx.frame + { + f(relative_objects) + } else { + Err(self.err( + ScErrorType::Context, + ScErrorCode::InternalError, + "accessing relative object table in non-VM frame", + &[], + )) + } + }) + } + pub(crate) fn with_current_prng(&self, f: F) -> Result where F: FnOnce(&mut Prng) -> Result, @@ -327,7 +357,7 @@ impl Host { /// frame at its top. pub(crate) fn get_current_contract_id_opt_internal(&self) -> Result, HostError> { self.with_current_frame(|frame| match frame { - Frame::ContractVM(vm, ..) => Ok(Some(vm.contract_id.metered_clone(&self.0.budget)?)), + Frame::ContractVM { vm, .. } => Ok(Some(vm.contract_id.metered_clone(&self.0.budget)?)), Frame::HostFunction(_) => Ok(None), Frame::Token(id, ..) => Ok(Some(id.metered_clone(&self.0.budget)?)), #[cfg(any(test, feature = "testutils"))] @@ -356,7 +386,7 @@ impl Host { // the previous frame must exist and must be a contract let hash = match frames.as_slice() { [.., c2, _] => match &c2.frame { - Frame::ContractVM(vm, ..) => Ok(vm.contract_id.metered_clone(&self.0.budget)?), + Frame::ContractVM { vm, .. } => Ok(vm.contract_id.metered_clone(&self.0.budget)?), Frame::HostFunction(_) => Err(self.err( ScErrorType::Context, ScErrorCode::UnexpectedType, @@ -385,7 +415,7 @@ impl Host { let st = match frames.as_slice() { // There are always two frames when WASM is executed in the VM. [.., c2, _] => match &c2.frame { - Frame::ContractVM(..) => Ok(InvokerType::Contract), + Frame::ContractVM { .. } => Ok(InvokerType::Contract), Frame::HostFunction(_) => Ok(InvokerType::Account), Frame::Token(..) => Ok(InvokerType::Contract), #[cfg(any(test, feature = "testutils"))] @@ -428,8 +458,8 @@ impl Host { fn call_contract_fn(&self, id: &Hash, func: &Symbol, args: &[Val]) -> Result { // Create key for storage let storage_key = self.contract_instance_ledger_key(id)?; - let contract_instance = self.retrieve_contract_instance_from_storage(&storage_key)?; - match &contract_instance.executable { + let instance = self.retrieve_contract_instance_from_storage(&storage_key)?; + match &instance.executable { ContractExecutable::Wasm(wasm_hash) => { let code_entry = self.retrieve_wasm_from_storage(&wasm_hash)?; let vm = Vm::new( @@ -437,13 +467,20 @@ impl Host { id.metered_clone(&self.0.budget)?, code_entry.as_slice(), )?; + let relative_objects = Vec::new(); self.with_frame( - Frame::ContractVM(vm.clone(), *func, args.to_vec(), contract_instance), + Frame::ContractVM { + vm: vm.clone(), + fn_name: *func, + args: args.to_vec(), + instance, + relative_objects, + }, || vm.invoke_function_raw(self, func, args), ) } ContractExecutable::Token => self.with_frame( - Frame::Token(id.clone(), *func, args.to_vec(), contract_instance), + Frame::Token(id.clone(), *func, args.to_vec(), instance), || { use crate::native_contract::{NativeContract, Token}; Token.call(func, self, args) @@ -480,7 +517,7 @@ impl Host { let mut is_last_non_host_frame = true; for ctx in self.try_borrow_context()?.iter().rev() { let exist_id = match &ctx.frame { - Frame::ContractVM(vm, ..) => &vm.contract_id, + Frame::ContractVM { vm, .. } => &vm.contract_id, Frame::Token(id, ..) => id, #[cfg(any(test, feature = "testutils"))] Frame::TestContract(tc) => &tc.id, @@ -683,7 +720,7 @@ impl Host { return Ok(()); } let storage_map = match &ctx.frame { - Frame::ContractVM(_, _, _, instance) => &instance.storage, + Frame::ContractVM { instance, .. } => &instance.storage, Frame::HostFunction(_) => { return Err(self.err( ScErrorType::Context, @@ -722,7 +759,7 @@ impl Host { return Ok(None); } let executable = match &ctx.frame { - Frame::ContractVM(_, _, _, instance) => { + Frame::ContractVM { instance, .. } => { instance.executable.metered_clone(self.budget_ref())? } Frame::HostFunction(_) => { diff --git a/soroban-env-host/src/host/mem_helper.rs b/soroban-env-host/src/host/mem_helper.rs index 97d19b029..fd7945deb 100644 --- a/soroban-env-host/src/host/mem_helper.rs +++ b/soroban-env-host/src/host/mem_helper.rs @@ -18,7 +18,7 @@ impl Host { let pos: u32 = pos.into(); let len: u32 = len.into(); self.with_current_frame(|frame| match frame { - Frame::ContractVM(vm, ..) => { + Frame::ContractVM { vm, .. } => { let vm = vm.clone(); Ok(VmSlice { vm, pos, len }) } @@ -67,7 +67,7 @@ impl Host { vm: &Rc, mem_pos: u32, buf: &[VAL], - to_le_bytes: impl Fn(&VAL) -> [u8; VAL_SZ], + to_le_bytes: impl Fn(&VAL) -> Result<[u8; VAL_SZ], HostError>, ) -> Result<(), HostError> { let val_sz = self.usize_to_u32(VAL_SZ)?; let len = self.usize_to_u32(buf.len())?; @@ -96,7 +96,7 @@ impl Host { &[], )); } - let tmp: [u8; VAL_SZ] = to_le_bytes(src); + let tmp: [u8; VAL_SZ] = to_le_bytes(src)?; dst.copy_from_slice(&tmp); } Ok(()) @@ -108,7 +108,7 @@ impl Host { vm: &Rc, mem_pos: u32, buf: &mut [VAL], - from_le_bytes: impl Fn(&[u8; VAL_SZ]) -> VAL, + from_le_bytes: impl Fn(&[u8; VAL_SZ]) -> Result, ) -> Result<(), HostError> { let val_sz = self.usize_to_u32(VAL_SZ)?; let len = self.usize_to_u32(buf.len())?; @@ -131,7 +131,7 @@ impl Host { for (dst, src) in buf.iter_mut().zip(mem_slice.chunks(VAL_SZ)) { if let Ok(src) = TryInto::<&[u8; VAL_SZ]>::try_into(src) { tmp.copy_from_slice(src); - *dst = from_le_bytes(&tmp); + *dst = from_le_bytes(&tmp)?; } else { // This should be impossible unless there's an error above, but just in case. return Err(self.err( diff --git a/soroban-env-host/src/host_object.rs b/soroban-env-host/src/host_object.rs index 004a707fa..dd3b0c038 100644 --- a/soroban-env-host/src/host_object.rs +++ b/soroban-env-host/src/host_object.rs @@ -1,6 +1,9 @@ +#![allow(dead_code)] + use soroban_env_common::{ - xdr::ContractCostType, Compare, DurationSmall, I128Small, I256Small, I64Small, SymbolSmall, - SymbolStr, Tag, TimepointSmall, U128Small, U256Small, U64Small, + xdr::{ContractCostType, ScErrorCode, ScErrorType}, + Compare, DurationSmall, I128Small, I256Small, I64Small, SymbolSmall, SymbolStr, Tag, + TimepointSmall, U128Small, U256Small, U64Small, }; use crate::{ @@ -169,7 +172,140 @@ declare_mem_host_object_type!(xdr::ScString, StringObject, String); declare_mem_host_object_type!(xdr::ScSymbol, SymbolObject, Symbol); declare_host_object_type!(xdr::ScAddress, AddressObject, Address); +// Objects come in two flavors: relative and absolute. They are differentiated +// by the low bit of the object handle: relative objects have 0, absolutes have +// 1. The remaining bits (left shifted by 1) are the index in a corresponding +// relative or absolute object table. +// +// Relative objects are the ones we pass to and from wasm/VM code, and are +// looked up in a per-VM-frame "relative objects" indirection table, to find an +// absolute object. Absolute objects are the underlying context-insensitive +// handles that point into the host object table (and so absolutes can also be +// used outside contexts, eg. in fields held in host objects themselves or while +// setting-up the host). Relative-to-absolute translation is done very close to +// the VM, when marshalling call args and return values (and host-function calls +// and returns). Host code should never see relative object handles, and if you +// ever try to look one up in the host object table, it will fail. +// +// The point of relative object handles is to isolate the objects seen by one VM +// from those seen by any other (and secondarily to avoid "system objects" like +// those allocated by the auth and event subsystems from perturbing object +// numbers seen by user code). User code should not perceive any objects other +// than ones they are specifically passed (or reachable through them). So their +// view of the world is limited to objects that made it into their relative +// object table. +// +// Also note: the relative/absolute object reference translation is _not_ done +// when running native contracts, either builtin or in local-testing mode, so +// you will not get identical object numbers in those cases. Since there is no +// real isolation between native contracts -- they can even dereference unsafe +// pointers if they want -- there's no point bothering with the translation (and +// there's no really obvious place to perform it systematically, like in the +// wasm marshalling path). + +pub fn is_relative_object_handle(handle: u32) -> bool { + handle & 1 == 0 +} + +pub fn handle_to_index(handle: u32) -> usize { + (handle as usize) >> 1 +} + +pub fn index_to_handle(host: &Host, index: usize, relative: bool) -> Result { + if let Ok(smaller) = u32::try_from(index) { + if let Some(shifted) = smaller.checked_shl(1) { + if relative { + return Ok(shifted); + } else { + return Ok(shifted | 1); + } + } + } + Err(host.err_arith_overflow()) +} + impl Host { + pub(crate) fn relative_to_absolute(&self, val: Val) -> Result { + if let Ok(obj) = Object::try_from(val) { + let handle = obj.get_handle(); + return if is_relative_object_handle(handle) { + let index = handle_to_index(handle); + let abs_opt = self.with_current_frame_relative_object_table(|table| { + Ok(table.get(index).map(|x| *x)) + })?; + match abs_opt { + Some(abs) if abs.to_val().get_tag() == val.get_tag() => Ok(abs.into()), + // User forged a type tag. This is _relatively_ harmless + // since we converted from relative to absolute and + // literally changed object references altogether while + // doing so -- i.e. we now have a correctly-typed absolute + // object reference we _could_ proceed to use as requested + // -- but a user passing an ill-typed relative object + // reference is probably either a bug or part of some + // strange type of attack, and in any case we _would_ signal + // this as an object-integrity type mismatch if we hadn't + // done the translation (eg. in native testing mode), so for + // symmetry sake we will return the same error here. + Some(_) => Err(self.err( + ScErrorType::Object, + ScErrorCode::UnexpectedType, + "relative and absolute object types differ", + &[], + )), + // User is referring to something outside the bounds of + // their relative table, erroneously. + None => Err(self.err( + ScErrorType::Context, + ScErrorCode::InvalidInput, + "unknown relative object reference", + &[Val::from_u32(handle).to_val()], + )), + } + } else { + // This also gets "invalid input" because it came from the user + // VM: they tried to forge an absolute. + Err(self.err( + ScErrorType::Context, + ScErrorCode::InvalidInput, + "relative_to_absolute given an absolute reference", + &[Val::from_u32(handle).to_val()], + )) + }; + } + Ok(val) + } + + pub(crate) fn absolute_to_relative(&self, val: Val) -> Result { + if let Ok(obj) = Object::try_from(val) { + let handle = obj.get_handle(); + return if is_relative_object_handle(handle) { + // This gets "internal error" because we should never have found + // ourselves in posession of a relative reference to return to + // the user VM in the first place. Logic bug. + Err(self.err( + ScErrorType::Context, + ScErrorCode::InternalError, + "absolute_to_relative given a relative reference", + // NB: we convert to a U32Val here otherwise the _events_ system + // will fault when trying to look up this argument as a relative + // object reference. + &[Val::from_u32(handle).to_val()], + )) + } else { + // Push a new entry into the relative-objects vector. + metered_clone::charge_heap_alloc::(1, self.as_budget())?; + let index = self.with_current_frame_relative_object_table(|table| { + let index = table.len(); + table.push(obj); + Ok(index) + })?; + let handle = index_to_handle(self, index, true)?; + Ok(Object::from_handle_and_tag(handle, val.get_tag()).into()) + }; + } + Ok(val) + } + /// Moves a value of some type implementing [`HostObjectType`] into the host's /// object array, returning a [`HostObj`] containing the new object's array /// index, tagged with the [`xdr::ScObjectType`]. @@ -177,15 +313,12 @@ impl Host { &self, hot: HOT, ) -> Result { - let prev_len = self.try_borrow_objects()?.len(); - if prev_len > u32::MAX as usize { - return Err(self.err_arith_overflow()); - } + let index = self.try_borrow_objects()?.len(); + let handle = index_to_handle(self, index, false)?; // charge for the new host object, which is just the amortized cost of a single // `HostObject` allocation metered_clone::charge_heap_alloc::(1, self.as_budget())?; self.try_borrow_objects_mut()?.push(HOT::inject(hot)); - let handle = prev_len as u32; Ok(HOT::new_from_handle(handle)) } @@ -203,7 +336,16 @@ impl Host { let r = self.try_borrow_objects()?; let obj: Object = obj.into(); let handle: u32 = obj.get_handle(); - f(r.get(handle as usize)) + if is_relative_object_handle(handle) { + Err(self.err( + ScErrorType::Context, + ScErrorCode::InternalError, + "looking up relative object", + &[Val::from_u32(handle).to_val()], + )) + } else { + f(r.get(handle_to_index(handle))) + } } pub(crate) fn check_val_integrity(&self, val: Val) -> Result<(), HostError> { diff --git a/soroban-env-host/src/test/basic.rs b/soroban-env-host/src/test/basic.rs index 64352090d..b3e13dc81 100644 --- a/soroban-env-host/src/test/basic.rs +++ b/soroban-env-host/src/test/basic.rs @@ -16,7 +16,7 @@ fn u64_roundtrip() -> Result<(), HostError> { let v2: Val = u2.try_into_val(&host)?; assert_eq!(v2.get_tag(), Tag::U64Object); let obj: Object = v2.try_into()?; - assert_eq!(obj.get_handle(), 0); + assert_eq!(obj.get_handle(), 1); let k = u64::try_from_val(&host, &v2)?; assert_eq!(u2, k); Ok(()) @@ -35,7 +35,7 @@ fn i64_roundtrip() -> Result<(), HostError> { let v2: Val = i2.try_into_val(&host)?; assert_eq!(v2.get_tag(), Tag::I64Object); let obj: Object = v2.try_into()?; - assert_eq!(obj.get_handle(), 0); + assert_eq!(obj.get_handle(), 1); let k = i64::try_from_val(&host, &v2)?; assert_eq!(i2, k); Ok(()) diff --git a/soroban-env-host/src/test/hostile.rs b/soroban-env-host/src/test/hostile.rs index 10b759028..53082be70 100644 --- a/soroban-env-host/src/test/hostile.rs +++ b/soroban-env-host/src/test/hostile.rs @@ -1,6 +1,6 @@ use soroban_env_common::{ xdr::{ScErrorCode, ScErrorType}, - Env, Symbol, + Env, EnvBase, Symbol, Val, VecObject, }; use soroban_test_wasms::HOSTILE; @@ -116,3 +116,77 @@ fn hostile_objs_traps() -> Result<(), HostError> { )); Ok(()) } + +#[test] +fn hostile_forged_objects_trap() -> Result<(), HostError> { + let host = Host::test_host_with_recording_footprint(); + let contract_id_obj = host.register_test_contract_wasm(HOSTILE); + + host.set_diagnostic_level(crate::DiagnosticLevel::Debug)?; + host.with_budget(|b| b.reset_default())?; + host.with_budget(|b| b.reset_unlimited_cpu())?; + + fn forged_val_to_forge_call_args(host: &Host, val: Val) -> Result { + let payload = val.get_payload(); + let lo = payload as u32; + let hi = (payload >> 32) as u32; + host.vec_new_from_slice(&[lo.into(), hi.into()]) + } + + // Here we're passing a vector of two numbers that, when reassembled into a + // payload and cast to an object, denote an absolute object reference. These + // should fail because relative-to-absolute conversion will reject it. + let absolute_vec = host.vec_new_from_slice(&[1u32.into(), 2u32.into()])?; + let res = host.call( + contract_id_obj, + Symbol::try_from_small_str("forgeref")?, + forged_val_to_forge_call_args(&host, absolute_vec.to_val())?, + ); + assert!(HostError::result_matches_err( + res.clone(), + (ScErrorType::Context, ScErrorCode::InvalidInput) + )); + + // Here we just pick a big handle number -- but with a zero bit set, so it's + // a relative handle -- to poke around "random object space" to see if we + // can get an object. This will fail because it doesn't denote anything in + // the relative object table (it's past the end). + let big_vec_ref = unsafe { VecObject::from_handle(0xffff0) }; + let res = host.call( + contract_id_obj, + Symbol::try_from_small_str("forgeref")?, + forged_val_to_forge_call_args(&host, big_vec_ref.to_val())?, + ); + assert!(HostError::result_matches_err( + res.clone(), + (ScErrorType::Context, ScErrorCode::InvalidInput) + )); + + // Here we call a function that tries to forge the type of an object + // reference and call a method on it. This fails in the relative-to-absolute + // conversion path, where we check identity of types. + let res = host.call( + contract_id_obj, + Symbol::try_from_small_str("forgety1")?, + host.vec_new_from_slice(&[absolute_vec.to_val()])?, + ); + assert!(HostError::result_matches_err( + res.clone(), + (ScErrorType::Object, ScErrorCode::UnexpectedType) + )); + + // Here we call a function that tries to forge the type of an object + // reference and just pass it as an _argument_ to another function. This + // fails in the same place as the previous test. + let res = host.call( + contract_id_obj, + Symbol::try_from_small_str("forgety2")?, + host.vec_new_from_slice(&[absolute_vec.to_val()])?, + ); + assert!(HostError::result_matches_err( + res.clone(), + (ScErrorType::Object, ScErrorCode::UnexpectedType) + )); + + Ok(()) +} diff --git a/soroban-env-host/src/test/vec.rs b/soroban-env-host/src/test/vec.rs index 480a858be..b9d87cd2b 100644 --- a/soroban-env-host/src/test/vec.rs +++ b/soroban-env-host/src/test/vec.rs @@ -16,8 +16,8 @@ fn vec_as_seen_by_host() -> Result<(), HostError> { assert_eq!(val1.get_tag(), Tag::VecObject); let obj0: Object = val0.try_into()?; let obj1: Object = val1.try_into()?; - assert_eq!(obj0.get_handle(), 0); - assert_eq!(obj1.get_handle(), 1); + assert_eq!(obj0.get_handle(), 1); + assert_eq!(obj1.get_handle(), 3); assert_eq!(obj0.as_val().get_tag(), Tag::VecObject); assert_eq!(obj1.as_val().get_tag(), Tag::VecObject); // Check that we got 2 distinct Vec objects diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs index 07d0e283a..0915cef82 100644 --- a/soroban-env-host/src/vm.rs +++ b/soroban-env-host/src/vm.rs @@ -272,9 +272,8 @@ impl Vm { } } } - Ok( - <_ as WasmiMarshal>::try_marshal_from_value(wasm_ret[0].clone()) - .ok_or(ConversionError)?, + host.relative_to_absolute( + Val::try_marshal_from_value(wasm_ret[0].clone()).ok_or(ConversionError)?, ) } @@ -287,8 +286,8 @@ impl Vm { host.charge_budget(ContractCostType::InvokeVmFunction, None)?; let wasm_args: Vec = args .iter() - .map(|i| Value::I64(i.get_payload() as i64)) - .collect(); + .map(|i| host.absolute_to_relative(*i).map(|v| v.marshal_from_self())) + .collect::, HostError>>()?; let func_ss: SymbolStr = func_sym.try_into_val(host)?; let ext = match self .instance diff --git a/soroban-env-host/src/vm/dispatch.rs b/soroban-env-host/src/vm/dispatch.rs index 505e97d67..4cd959e12 100644 --- a/soroban-env-host/src/vm/dispatch.rs +++ b/soroban-env-host/src/vm/dispatch.rs @@ -1,9 +1,9 @@ use super::FuelRefillable; use crate::{xdr::ContractCostType, Host, HostError, VmCaller, VmCallerEnv}; use crate::{ - AddressObject, BytesObject, DurationObject, Error, I128Object, I256Object, I256Val, I64Object, - MapObject, StorageType, StringObject, Symbol, SymbolObject, TimepointObject, U128Object, - U256Object, U256Val, U32Val, U64Object, Val, VecObject, + AddressObject, Bool, BytesObject, DurationObject, Error, I128Object, I256Object, I256Val, + I32Val, I64Object, MapObject, StorageType, StringObject, Symbol, SymbolObject, TimepointObject, + U128Object, U256Object, U256Val, U32Val, U64Object, U64Val, Val, VecObject, Void, }; use soroban_env_common::{call_macro_with_all_host_functions, WasmiMarshal}; use wasmi::{ @@ -11,6 +11,76 @@ use wasmi::{ Value, }; +pub(crate) trait RelativeObjectConversion: WasmiMarshal { + fn absolute_to_relative(self, _host: &Host) -> Result { + Ok(self) + } + fn relative_to_absolute(self, _host: &Host) -> Result { + Ok(self) + } + fn try_marshal_from_relative_value(v: wasmi::Value, host: &Host) -> Result { + let val = Self::try_marshal_from_value(v).ok_or(BadSignature)?; + Ok(val.relative_to_absolute(host)?) + } + fn marshal_relative_from_self(self, host: &Host) -> Result { + let rel = self.absolute_to_relative(host)?; + Ok(Self::marshal_from_self(rel)) + } +} + +macro_rules! impl_relative_object_conversion { + ($T:ty) => { + impl RelativeObjectConversion for $T { + fn absolute_to_relative(self, host: &Host) -> Result { + Ok(Self::try_from(host.absolute_to_relative(self.into())?)?) + } + + fn relative_to_absolute(self, host: &Host) -> Result { + Ok(Self::try_from(host.relative_to_absolute(self.into())?)?) + } + } + }; +} + +// Define a relative-to-absolute impl for any type that is (a) mentioned +// in a host function type signature in env and (b) might possibly carry an +// object reference. If you miss one, this file won't compile, so it's safe. +impl_relative_object_conversion!(Val); +impl_relative_object_conversion!(Symbol); + +impl_relative_object_conversion!(AddressObject); +impl_relative_object_conversion!(BytesObject); +impl_relative_object_conversion!(DurationObject); + +impl_relative_object_conversion!(TimepointObject); +impl_relative_object_conversion!(SymbolObject); +impl_relative_object_conversion!(StringObject); + +impl_relative_object_conversion!(VecObject); +impl_relative_object_conversion!(MapObject); + +impl_relative_object_conversion!(I64Object); +impl_relative_object_conversion!(I128Object); +impl_relative_object_conversion!(I256Object); + +impl_relative_object_conversion!(U64Object); +impl_relative_object_conversion!(U128Object); +impl_relative_object_conversion!(U256Object); + +impl_relative_object_conversion!(U64Val); +impl_relative_object_conversion!(U256Val); +impl_relative_object_conversion!(I256Val); + +// Trivial / non-relativizing impls are ok for types that can't carry objects. +impl RelativeObjectConversion for i64 {} +impl RelativeObjectConversion for u64 {} +impl RelativeObjectConversion for Void {} +impl RelativeObjectConversion for Bool {} +impl RelativeObjectConversion for Error {} +impl RelativeObjectConversion for StorageType {} +impl RelativeObjectConversion for U32Val {} +impl RelativeObjectConversion for I32Val {} + /////////////////////////////////////////////////////////////////////////////// /// X-macro use: dispatch functions /////////////////////////////////////////////////////////////////////////////// @@ -90,11 +160,11 @@ macro_rules! generate_dispatch_functions { // happens to be a natural switching point for that: we have // conversions to and from both Val and i64 / u64 for // wasmi::Value. - let res: Result<_, HostError> = host.$fn_id(&mut vmcaller, $(<$type>::try_marshal_from_value(Value::I64($arg)).ok_or(BadSignature)?),*); + let res: Result<_, HostError> = host.$fn_id(&mut vmcaller, $(<$type>::try_marshal_from_relative_value(Value::I64($arg), &host)?),*); let res = match res { Ok(ok) => { - let val: Value = ok.marshal_from_self(); + let val: Value = ok.marshal_relative_from_self(&host)?; if let Value::I64(v) = val { Ok((v,)) } else { diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock index e9e081e28..69f928e38 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.lock +++ b/soroban-test-wasms/wasm-workspace/Cargo.lock @@ -447,6 +447,7 @@ dependencies = [ name = "example_hostile" version = "0.0.0" dependencies = [ + "soroban-env-common", "soroban-sdk", ] @@ -1051,7 +1052,7 @@ dependencies = [ "soroban-env-macros", "soroban-wasmi", "static_assertions", - "stellar-xdr", + "stellar-xdr 0.0.17 (git+https://github.com/stellar/rs-stellar-xdr?rev=54c183e91573d8a5882ff1dda21d444ef42cc426)", ] [[package]] @@ -1096,14 +1097,14 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr", + "stellar-xdr 0.0.17 (git+https://github.com/stellar/rs-stellar-xdr?rev=54c183e91573d8a5882ff1dda21d444ef42cc426)", "syn 2.0.18", "thiserror", ] [[package]] name = "soroban-ledger-snapshot" -version = "0.9.1" +version = "0.9.2" dependencies = [ "serde", "serde_json", @@ -1124,7 +1125,7 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "0.9.1" +version = "0.9.2" dependencies = [ "arbitrary", "bytes-lit", @@ -1140,7 +1141,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "0.9.1" +version = "0.9.2" dependencies = [ "crate-git-revision", "darling", @@ -1152,30 +1153,30 @@ dependencies = [ "soroban-env-common", "soroban-spec", "soroban-spec-rust", - "stellar-xdr", + "stellar-xdr 0.0.17 (git+https://github.com/stellar/rs-stellar-xdr?rev=0f16673441898162c9996da6117be2280ef8fd84)", "syn 2.0.18", ] [[package]] name = "soroban-spec" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.13.1", - "stellar-xdr", + "stellar-xdr 0.0.17 (git+https://github.com/stellar/rs-stellar-xdr?rev=0f16673441898162c9996da6117be2280ef8fd84)", "thiserror", "wasmparser", ] [[package]] name = "soroban-spec-rust" -version = "0.9.1" +version = "0.9.2" dependencies = [ "prettyplease", "proc-macro2", "quote", "sha2 0.9.9", "soroban-spec", - "stellar-xdr", + "stellar-xdr 0.0.17 (git+https://github.com/stellar/rs-stellar-xdr?rev=0f16673441898162c9996da6117be2280ef8fd84)", "syn 2.0.18", "thiserror", ] @@ -1228,6 +1229,17 @@ dependencies = [ name = "stellar-xdr" version = "0.0.17" source = "git+https://github.com/stellar/rs-stellar-xdr?rev=0f16673441898162c9996da6117be2280ef8fd84#0f16673441898162c9996da6117be2280ef8fd84" +dependencies = [ + "crate-git-revision", + "hex", + "serde", + "serde_with", +] + +[[package]] +name = "stellar-xdr" +version = "0.0.17" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=54c183e91573d8a5882ff1dda21d444ef42cc426#54c183e91573d8a5882ff1dda21d444ef42cc426" dependencies = [ "arbitrary", "base64 0.13.1", diff --git a/soroban-test-wasms/wasm-workspace/Cargo.toml b/soroban-test-wasms/wasm-workspace/Cargo.toml index be58d2b52..a43e70f5d 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.toml +++ b/soroban-test-wasms/wasm-workspace/Cargo.toml @@ -43,6 +43,13 @@ panic = "abort" codegen-units = 1 lto = true +[workspace.dependencies.soroban-sdk] +version = "0.9.2" +git = "https://github.com/stellar/rs-soroban-sdk" + +[workspace.dependencies.soroban-env-common] +version = "0.0.17" +git = "https://github.com/stellar/rs-soroban-env" # Always build from the local instance of env as we need to rebuild test WASMs # only due to the env changes. @@ -51,10 +58,6 @@ soroban-env-common = { path = "../../soroban-env-common" } soroban-env-guest = { path = "../../soroban-env-guest" } soroban-env-host = { path = "../../soroban-env-host" } -[workspace.dependencies.soroban-sdk] -version = "0.9.1" -git = "https://github.com/stellar/rs-soroban-sdk" - # Always build using the local SDK. Usually the env change is accompanied with # the corresponding SDK change. [patch."https://github.com/stellar/rs-soroban-sdk"] diff --git a/soroban-test-wasms/wasm-workspace/hostile/Cargo.toml b/soroban-test-wasms/wasm-workspace/hostile/Cargo.toml index 4ce36a014..412f25a38 100644 --- a/soroban-test-wasms/wasm-workspace/hostile/Cargo.toml +++ b/soroban-test-wasms/wasm-workspace/hostile/Cargo.toml @@ -12,4 +12,5 @@ crate-type = ["cdylib", "rlib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } \ No newline at end of file +soroban-sdk = { workspace = true } +soroban-env-common = { workspace = true } \ No newline at end of file diff --git a/soroban-test-wasms/wasm-workspace/hostile/src/lib.rs b/soroban-test-wasms/wasm-workspace/hostile/src/lib.rs index 53eafb863..703fe8ec3 100644 --- a/soroban-test-wasms/wasm-workspace/hostile/src/lib.rs +++ b/soroban-test-wasms/wasm-workspace/hostile/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, Bytes, Env}; +use soroban_env_common::BytesObject; +use soroban_sdk::{contract, contractimpl, Bytes, Env, FromVal, Val, Vec}; #[contract] pub struct Contract; @@ -57,4 +58,29 @@ impl Contract { Bytes::from_slice(&env, &local_buf); } } + + pub fn forgeref(env: Env, lo: u32, hi: u32) -> u32 { + let payload: u64 = lo as u64 | ((hi as u64) << 32); + let v: Vec = Vec::from_val(&env, &Val::from_payload(payload)); + v.get(0).unwrap() + } + + // Forge a type and call a method on it. + pub fn forgety1(env: Env, v: Vec) -> u32 { + let b = Bytes::from_val( + &env, + &unsafe { BytesObject::from_handle(v.to_object().get_handle()) }.to_val(), + ); + b.get(0).unwrap() as u32 + } + + // Forge a type and pass it as an argument. + #[allow(unused_mut)] + pub fn forgety2(env: Env, mut v: Vec) { + let b = Bytes::from_val( + &env, + &unsafe { BytesObject::from_handle(v.to_object().get_handle()) }.to_val(), + ); + v.push_back(b) + } } diff --git a/soroban-test-wasms/wasm-workspace/opt/auth_test_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/auth_test_contract.wasm index e8d421cb3..fe4bc8551 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/auth_test_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/auth_test_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm b/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm index d52ad8e21..20079cd8b 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm b/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm index 19ad84df8..1a5228797 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm b/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm index ab6b0c182..5c1d3f352 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm b/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm index 72dacabbd..63f87d26f 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm index 178be909c..e9e776c68 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm b/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm index 9f55bb69a..ecbb005dc 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm b/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm index 453dc776b..f534c74ed 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm b/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm index 94e812d48..a15d3cb47 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm index b419f25df..24a83ce98 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm b/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm index 6770bfbb1..97a41c79e 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm b/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm index 9ac928732..15ed616c5 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_updateable_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/example_updateable_contract.wasm index 4100c10ed..a8f93a85b 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_updateable_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_updateable_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm b/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm index 966b510cb..c2a9ee21f 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/test_delegated_account.wasm b/soroban-test-wasms/wasm-workspace/opt/test_delegated_account.wasm index 737913fe3..076c30bc1 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/test_delegated_account.wasm and b/soroban-test-wasms/wasm-workspace/opt/test_delegated_account.wasm differ