1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
#![allow(dead_code)]
use crate::{
budget::Budget,
host::{
metered_clone::{self, MeteredClone},
metered_map::MeteredOrdMap,
metered_vector::MeteredVector,
},
num::{I256, U256},
xdr::{self, ContractCostType, ScErrorCode, ScErrorType},
AddressObject, BytesObject, Compare, DurationObject, DurationSmall, Host, HostError,
I128Object, I128Small, I256Object, I256Small, I64Object, I64Small, MapObject, Object,
StringObject, SymbolObject, SymbolSmall, SymbolStr, TimepointObject, TimepointSmall,
TryFromVal, U128Object, U128Small, U256Object, U256Small, U64Object, U64Small, Val, VecObject,
};
pub(crate) type HostMap = MeteredOrdMap<Val, Val, Host>;
pub(crate) type HostVec = MeteredVector<Val>;
#[cfg(feature = "testutils")]
impl std::hash::Hash for HostVec {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.vec.len().hash(state);
for x in self.vec.iter() {
x.get_payload().hash(state);
}
}
}
#[cfg(feature = "testutils")]
impl std::hash::Hash for HostMap {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.len().hash(state);
for (k, v) in self.map.iter() {
k.get_payload().hash(state);
v.get_payload().hash(state);
}
}
}
#[derive(Clone)]
#[cfg_attr(feature = "testutils", derive(Hash))]
pub(crate) enum HostObject {
Vec(HostVec),
Map(HostMap),
U64(u64),
I64(i64),
TimePoint(xdr::TimePoint),
Duration(xdr::Duration),
U128(u128),
I128(i128),
U256(U256),
I256(I256),
Bytes(xdr::ScBytes),
String(xdr::ScString),
Symbol(xdr::ScSymbol),
Address(xdr::ScAddress),
}
impl HostObject {
// Temporarily performs a shallow comparison against a Val of the
// associated small value type, returning None if the Val is of
// the wrong type.
pub(crate) fn try_compare_to_small(
&self,
budget: &Budget,
rv: Val,
) -> Result<Option<core::cmp::Ordering>, HostError> {
let res = match self {
HostObject::U64(u) => {
let Ok(small) = U64Small::try_from(rv) else {
return Ok(None);
};
let small: u64 = small.into();
Some(budget.compare(u, &small)?)
}
HostObject::I64(i) => {
let Ok(small) = I64Small::try_from(rv) else {
return Ok(None);
};
let small: i64 = small.into();
Some(budget.compare(i, &small)?)
}
HostObject::TimePoint(tp) => {
let Ok(small) = TimepointSmall::try_from(rv) else {
return Ok(None);
};
let small: u64 = small.into();
Some(budget.compare(&tp.0, &small)?)
}
HostObject::Duration(d) => {
let Ok(small) = DurationSmall::try_from(rv) else {
return Ok(None);
};
let small: u64 = small.into();
Some(budget.compare(&d.0, &small)?)
}
HostObject::U128(u) => {
let Ok(small) = U128Small::try_from(rv) else {
return Ok(None);
};
let small: u128 = small.into();
Some(budget.compare(u, &small)?)
}
HostObject::I128(i) => {
let Ok(small) = I128Small::try_from(rv) else {
return Ok(None);
};
let small: i128 = small.into();
Some(budget.compare(i, &small)?)
}
HostObject::U256(u) => {
let Ok(small) = U256Small::try_from(rv) else {
return Ok(None);
};
let small: U256 = small.into();
Some(budget.compare(u, &small)?)
}
HostObject::I256(i) => {
let Ok(small) = I256Small::try_from(rv) else {
return Ok(None);
};
let small: I256 = small.into();
Some(budget.compare(i, &small)?)
}
HostObject::Symbol(s) => {
let Ok(small) = SymbolSmall::try_from(rv) else {
return Ok(None);
};
let small: SymbolStr = small.into();
let rhs: &[u8] = small.as_ref();
Some(budget.compare(&s.as_vec().as_slice(), &rhs)?)
}
HostObject::Vec(_)
| HostObject::Map(_)
| HostObject::Bytes(_)
| HostObject::String(_)
| HostObject::Address(_) => None,
};
Ok(res)
}
}
pub(crate) trait HostObjectType: MeteredClone {
type Wrapper: Into<Object>;
fn new_from_handle(handle: u32) -> Self::Wrapper;
fn inject(self) -> HostObject;
fn try_extract(obj: &HostObject) -> Option<&Self>;
}
// Some host objects are "a slab of memory" which we want
// to treat fairly uniformly in memory-related host functions.
pub(crate) trait MemHostObjectType:
HostObjectType + TryFrom<Vec<u8>, Error = xdr::Error> + Into<Vec<u8>>
{
fn as_byte_slice(&self) -> &[u8];
}
macro_rules! declare_host_object_type {
($TY:ty, $TAG:ident, $CASE:ident) => {
impl HostObjectType for $TY {
type Wrapper = $TAG;
fn new_from_handle(handle: u32) -> Self::Wrapper {
unsafe { $TAG::from_handle(handle) }
}
fn inject(self) -> HostObject {
HostObject::$CASE(self)
}
fn try_extract(obj: &HostObject) -> Option<&Self> {
match obj {
HostObject::$CASE(v) => Some(v),
_ => None,
}
}
}
};
}
macro_rules! declare_mem_host_object_type {
($TY:ty, $TAG:ident, $CASE:ident) => {
declare_host_object_type!($TY, $TAG, $CASE);
impl MemHostObjectType for $TY {
fn as_byte_slice(&self) -> &[u8] {
self.as_slice()
}
}
};
}
// ${type of contained data}, ${object-wrapper common type}, ${case in HostObject}
declare_host_object_type!(HostMap, MapObject, Map);
declare_host_object_type!(HostVec, VecObject, Vec);
declare_host_object_type!(u64, U64Object, U64);
declare_host_object_type!(i64, I64Object, I64);
declare_host_object_type!(xdr::TimePoint, TimepointObject, TimePoint);
declare_host_object_type!(xdr::Duration, DurationObject, Duration);
declare_host_object_type!(u128, U128Object, U128);
declare_host_object_type!(i128, I128Object, I128);
declare_host_object_type!(U256, U256Object, U256);
declare_host_object_type!(I256, I256Object, I256);
declare_mem_host_object_type!(xdr::ScBytes, BytesObject, Bytes);
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 -- the lack of translation is not exactly making the
// security of native testing worse than it already is. But it does reduce the
// fidelity of VM-mode simulation in native testing mode. See
// https://github.com/stellar/rs-soroban-env/issues/1286 for a planned fix.
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<u32, HostError> {
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<Val, HostError> {
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::Value,
ScErrorCode::InvalidInput,
"relative and absolute object types differ",
&[],
)),
// User is referring to something outside the bounds of
// their relative table, erroneously.
None => Err(self.err(
ScErrorType::Value,
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::Value,
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<Val, HostError> {
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::<Object>(1, self)?;
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 the associated [`Object`] wrapper type
/// containing the new object's handle.
pub(crate) fn add_host_object<HOT: HostObjectType>(
&self,
hot: HOT,
) -> Result<HOT::Wrapper, HostError> {
let _span = tracy_span!("add host object");
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::<HostObject>(1, self)?;
self.try_borrow_objects_mut()?.push(HOT::inject(hot));
Ok(HOT::new_from_handle(handle))
}
pub(crate) fn visit_obj_untyped<F, U>(
&self,
obj: impl Into<Object>,
f: F,
) -> Result<U, HostError>
where
F: FnOnce(&HostObject) -> Result<U, HostError>,
{
let _span = tracy_span!("visit host object");
// `VisitObject` covers the cost of visiting an object. The actual cost
// of the closure needs to be covered by the caller. Although each visit
// does small amount of work -- getting the object handling and indexing
// into the host object buffer, almost too little to bother charging for
// -- it is ubiquitous and therefore we charge budget here for safety /
// future proofing.
self.charge_budget(ContractCostType::VisitObject, None)?;
let r = self.try_borrow_objects()?;
let obj: Object = obj.into();
let handle: u32 = obj.get_handle();
if is_relative_object_handle(handle) {
// This should never happen: we should have translated a relative
// object handle to an absolute before we got here.
Err(self.err(
ScErrorType::Object,
ScErrorCode::InternalError,
"looking up relative object",
&[Val::from_u32(handle).to_val()],
))
} else if let Some(obj) = r.get(handle_to_index(handle)) {
f(obj)
} else {
// Discard the broken object here instead of including
// it in the error to avoid further attempts to interpret it.
// e.g. if diagnostics are on, then this would immediately
// begin recursing, attempting and failing to externalize
// debug info for this very error. Store the u64 payload instead.
let obj_payload = obj.as_val().get_payload();
let payload_val = Val::try_from_val(self, &obj_payload)?;
Err(self.err(
ScErrorType::Value,
ScErrorCode::InvalidInput,
"unknown object reference",
&[payload_val],
))
}
}
// Notes on metering: object visiting part is covered by
// [`Host::visit_obj_untyped`]. Closure needs to be metered separately.
pub(crate) fn visit_obj<HOT: HostObjectType, F, U>(
&self,
obj: HOT::Wrapper,
f: F,
) -> Result<U, HostError>
where
F: FnOnce(&HOT) -> Result<U, HostError>,
{
self.visit_obj_untyped(obj, |hobj| match HOT::try_extract(hobj) {
// This should never happen: we should have rejected a mis-tagged
// object handle before it got here.
None => Err(self.err(
xdr::ScErrorType::Object,
xdr::ScErrorCode::InternalError,
"object reference type does not match tag",
&[],
)),
Some(hot) => f(hot),
})
}
}