soroban_env_host_zephyr/
host_object.rs

1#![allow(dead_code)]
2
3use crate::{
4    budget::Budget,
5    host::{
6        metered_clone::{self, MeteredClone},
7        metered_map::MeteredOrdMap,
8        metered_vector::MeteredVector,
9    },
10    num::{I256, U256},
11    xdr::{self, ContractCostType, ScErrorCode, ScErrorType, SCSYMBOL_LIMIT},
12    AddressObject, BytesObject, Compare, DurationObject, DurationSmall, Host, HostError,
13    I128Object, I128Small, I256Object, I256Small, I64Object, I64Small, MapObject, Object,
14    StringObject, SymbolObject, SymbolSmall, SymbolStr, TimepointObject, TimepointSmall,
15    TryFromVal, U128Object, U128Small, U256Object, U256Small, U64Object, U64Small, Val, VecObject,
16};
17
18pub(crate) type HostMap = MeteredOrdMap<Val, Val, Host>;
19pub(crate) type HostVec = MeteredVector<Val>;
20
21#[derive(Clone, Hash)]
22pub enum HostObject {
23    Vec(HostVec),
24    Map(HostMap),
25    U64(u64),
26    I64(i64),
27    TimePoint(xdr::TimePoint),
28    Duration(xdr::Duration),
29    U128(u128),
30    I128(i128),
31    U256(U256),
32    I256(I256),
33    Bytes(xdr::ScBytes),
34    String(xdr::ScString),
35    Symbol(xdr::ScSymbol),
36    Address(xdr::ScAddress),
37}
38
39impl std::fmt::Debug for HostObject {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Vec(arg0) => f.debug_tuple("Vec").field(&arg0.len()).finish(),
43            Self::Map(arg0) => f.debug_tuple("Map").field(&arg0.len()).finish(),
44            Self::U64(arg0) => f.debug_tuple("U64").field(arg0).finish(),
45            Self::I64(arg0) => f.debug_tuple("I64").field(arg0).finish(),
46            Self::TimePoint(arg0) => f.debug_tuple("TimePoint").field(arg0).finish(),
47            Self::Duration(arg0) => f.debug_tuple("Duration").field(arg0).finish(),
48            Self::U128(arg0) => f.debug_tuple("U128").field(arg0).finish(),
49            Self::I128(arg0) => f.debug_tuple("I128").field(arg0).finish(),
50            Self::U256(arg0) => f.debug_tuple("U256").field(arg0).finish(),
51            Self::I256(arg0) => f.debug_tuple("I256").field(arg0).finish(),
52            Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(),
53            Self::String(arg0) => f.debug_tuple("String").field(arg0).finish(),
54            Self::Symbol(arg0) => f.debug_tuple("Symbol").field(arg0).finish(),
55            Self::Address(arg0) => f.debug_tuple("Address").field(arg0).finish(),
56        }
57    }
58}
59
60impl HostObject {
61    // Temporarily performs a shallow comparison against a Val of the
62    // associated small value type, returning None if the Val is of
63    // the wrong type.
64    pub(crate) fn try_compare_to_small(
65        &self,
66        budget: &Budget,
67        rv: Val,
68    ) -> Result<Option<core::cmp::Ordering>, HostError> {
69        let res = match self {
70            HostObject::U64(u) => {
71                let Ok(small) = U64Small::try_from(rv) else {
72                    return Ok(None);
73                };
74                let small: u64 = small.into();
75                Some(budget.compare(u, &small)?)
76            }
77            HostObject::I64(i) => {
78                let Ok(small) = I64Small::try_from(rv) else {
79                    return Ok(None);
80                };
81                let small: i64 = small.into();
82                Some(budget.compare(i, &small)?)
83            }
84            HostObject::TimePoint(tp) => {
85                let Ok(small) = TimepointSmall::try_from(rv) else {
86                    return Ok(None);
87                };
88                let small: u64 = small.into();
89                Some(budget.compare(&tp.0, &small)?)
90            }
91            HostObject::Duration(d) => {
92                let Ok(small) = DurationSmall::try_from(rv) else {
93                    return Ok(None);
94                };
95                let small: u64 = small.into();
96                Some(budget.compare(&d.0, &small)?)
97            }
98            HostObject::U128(u) => {
99                let Ok(small) = U128Small::try_from(rv) else {
100                    return Ok(None);
101                };
102                let small: u128 = small.into();
103                Some(budget.compare(u, &small)?)
104            }
105            HostObject::I128(i) => {
106                let Ok(small) = I128Small::try_from(rv) else {
107                    return Ok(None);
108                };
109                let small: i128 = small.into();
110                Some(budget.compare(i, &small)?)
111            }
112            HostObject::U256(u) => {
113                let Ok(small) = U256Small::try_from(rv) else {
114                    return Ok(None);
115                };
116                let small: U256 = small.into();
117                Some(budget.compare(u, &small)?)
118            }
119            HostObject::I256(i) => {
120                let Ok(small) = I256Small::try_from(rv) else {
121                    return Ok(None);
122                };
123                let small: I256 = small.into();
124                Some(budget.compare(i, &small)?)
125            }
126            HostObject::Symbol(s) => {
127                let Ok(small) = SymbolSmall::try_from(rv) else {
128                    return Ok(None);
129                };
130                let small: SymbolStr = small.into();
131                let rhs: &[u8] = small.as_ref();
132                Some(budget.compare(&s.as_vec().as_slice(), &rhs)?)
133            }
134
135            HostObject::Vec(_)
136            | HostObject::Map(_)
137            | HostObject::Bytes(_)
138            | HostObject::String(_)
139            | HostObject::Address(_) => None,
140        };
141        Ok(res)
142    }
143}
144
145pub trait HostObjectType: MeteredClone {
146    type Wrapper: Into<Object>;
147    fn new_from_handle(handle: u32) -> Self::Wrapper;
148    fn inject(self) -> HostObject;
149    fn try_extract(obj: &HostObject) -> Option<&Self>;
150}
151
152// Some host objects are "a slab of memory" which we want
153// to treat fairly uniformly in memory-related host functions.
154pub(crate) trait MemHostObjectType:
155    HostObjectType + TryFrom<Vec<u8>, Error = xdr::Error> + Into<Vec<u8>>
156{
157    fn try_from_bytes(host: &Host, bytes: Vec<u8>) -> Result<Self, HostError>;
158    fn as_byte_slice(&self) -> &[u8];
159}
160
161macro_rules! declare_host_object_type {
162    ($TY:ty, $TAG:ident, $CASE:ident) => {
163        impl HostObjectType for $TY {
164            type Wrapper = $TAG;
165            fn new_from_handle(handle: u32) -> Self::Wrapper {
166                unsafe { $TAG::from_handle(handle) }
167            }
168            fn inject(self) -> HostObject {
169                HostObject::$CASE(self)
170            }
171
172            fn try_extract(obj: &HostObject) -> Option<&Self> {
173                match obj {
174                    HostObject::$CASE(v) => Some(v),
175                    _ => None,
176                }
177            }
178        }
179    };
180}
181
182macro_rules! declare_mem_host_object_type {
183    ($TY:ty, $TAG:ident, $CASE:ident) => {
184        declare_host_object_type!($TY, $TAG, $CASE);
185        impl MemHostObjectType for $TY {
186            fn try_from_bytes(_host: &Host, bytes: Vec<u8>) -> Result<Self, HostError> {
187                Self::try_from(bytes).map_err(Into::into)
188            }
189
190            fn as_byte_slice(&self) -> &[u8] {
191                self.as_slice()
192            }
193        }
194    };
195}
196
197// ${type of contained data}, ${object-wrapper common type}, ${case in HostObject}
198declare_host_object_type!(HostMap, MapObject, Map);
199declare_host_object_type!(HostVec, VecObject, Vec);
200declare_host_object_type!(u64, U64Object, U64);
201declare_host_object_type!(i64, I64Object, I64);
202declare_host_object_type!(xdr::TimePoint, TimepointObject, TimePoint);
203declare_host_object_type!(xdr::Duration, DurationObject, Duration);
204declare_host_object_type!(u128, U128Object, U128);
205declare_host_object_type!(i128, I128Object, I128);
206declare_host_object_type!(U256, U256Object, U256);
207declare_host_object_type!(I256, I256Object, I256);
208declare_mem_host_object_type!(xdr::ScBytes, BytesObject, Bytes);
209declare_mem_host_object_type!(xdr::ScString, StringObject, String);
210declare_host_object_type!(xdr::ScSymbol, SymbolObject, Symbol);
211declare_host_object_type!(xdr::ScAddress, AddressObject, Address);
212
213impl MemHostObjectType for xdr::ScSymbol {
214    fn try_from_bytes(host: &Host, bytes: Vec<u8>) -> Result<Self, HostError> {
215        if bytes.len() as u64 > SCSYMBOL_LIMIT {
216            return Err(host.err(
217                ScErrorType::Value,
218                ScErrorCode::InvalidInput,
219                "slice is too long to be represented as Symbol",
220                &[(bytes.len() as u32).into()],
221            ));
222        }
223        for b in &bytes {
224            SymbolSmall::validate_byte(*b).map_err(|_| {
225                host.err(
226                    ScErrorType::Value,
227                    ScErrorCode::InvalidInput,
228                    "byte is not allowed in Symbol",
229                    &[(*b as u32).into()],
230                )
231            })?;
232        }
233        Self::try_from(bytes).map_err(Into::into)
234    }
235    fn as_byte_slice(&self) -> &[u8] {
236        self.as_ref()
237    }
238}
239
240// Objects come in two flavors: relative and absolute. They are differentiated
241// by the low bit of the object handle: relative objects have 0, absolutes have
242// 1. The remaining bits (left shifted by 1) are the index in a corresponding
243// relative or absolute object table.
244//
245// Relative objects are the ones we pass to and from wasm/VM code, and are
246// looked up in a per-VM-frame "relative objects" indirection table, to find an
247// absolute object. Absolute objects are the underlying context-insensitive
248// handles that point into the host object table (and so absolutes can also be
249// used outside contexts, eg. in fields held in host objects themselves or while
250// setting-up the host). Relative-to-absolute translation is done very close to
251// the VM, when marshalling call args and return values (and host-function calls
252// and returns). Host code should never see relative object handles, and if you
253// ever try to look one up in the host object table, it will fail.
254//
255// The point of relative object handles is to isolate the objects seen by one VM
256// from those seen by any other (and secondarily to avoid "system objects" like
257// those allocated by the auth and event subsystems from perturbing object
258// numbers seen by user code). User code should not perceive any objects other
259// than ones they are specifically passed (or reachable through them). So their
260// view of the world is limited to objects that made it into their relative
261// object table.
262//
263// Also note: the relative/absolute object reference translation is _not_ done
264// when running native contracts, either builtin or in local-testing mode, so
265// you will not get identical object numbers in those cases. Since there is no
266// real isolation between native contracts -- they can even dereference unsafe
267// pointers if they want -- the lack of translation is not exactly making the
268// security of native testing worse than it already is. But it does reduce the
269// fidelity of VM-mode simulation in native testing mode. See
270// https://github.com/stellar/rs-soroban-env/issues/1286 for a planned fix.
271
272pub fn is_relative_object_handle(handle: u32) -> bool {
273    handle & 1 == 0
274}
275
276pub fn handle_to_index(handle: u32) -> usize {
277    (handle as usize) >> 1
278}
279
280pub fn index_to_handle(host: &Host, index: usize, relative: bool) -> Result<u32, HostError> {
281    if let Ok(smaller) = u32::try_from(index) {
282        if let Some(shifted) = smaller.checked_shl(1) {
283            if relative {
284                return Ok(shifted);
285            } else {
286                return Ok(shifted | 1);
287            }
288        }
289    }
290    Err(host.err_arith_overflow())
291}
292
293impl Host {
294    pub fn relative_to_absolute(&self, val: Val) -> Result<Val, HostError> {
295        if let Ok(obj) = Object::try_from(val) {
296            let handle = obj.get_handle();
297            return if is_relative_object_handle(handle) {
298                let index = handle_to_index(handle);
299                let abs_opt = self.with_current_frame_relative_object_table(|table| {
300                    Ok(table.get(index).map(|x| *x))
301                })?;
302                match abs_opt {
303                    Some(abs) if abs.to_val().get_tag() == val.get_tag() => Ok(abs.into()),
304                    // User forged a type tag. This is _relatively_ harmless
305                    // since we converted from relative to absolute and
306                    // literally changed object references altogether while
307                    // doing so -- i.e. we now have a correctly-typed absolute
308                    // object reference we _could_ proceed to use as requested
309                    // -- but a user passing an ill-typed relative object
310                    // reference is probably either a bug or part of some
311                    // strange type of attack, and in any case we _would_ signal
312                    // this as an object-integrity type mismatch if we hadn't
313                    // done the translation (eg. in native testing mode), so for
314                    // symmetry sake we will return the same error here.
315                    Some(_) => Err(self.err(
316                        ScErrorType::Value,
317                        ScErrorCode::InvalidInput,
318                        "relative and absolute object types differ",
319                        &[],
320                    )),
321                    // User is referring to something outside the bounds of
322                    // their relative table, erroneously.
323                    None => Err(self.err(
324                        ScErrorType::Value,
325                        ScErrorCode::InvalidInput,
326                        "unknown relative object reference",
327                        &[Val::from_u32(handle).to_val()],
328                    )),
329                }
330            } else {
331                // This also gets "invalid input" because it came from the user
332                // VM: they tried to forge an absolute.
333                Err(self.err(
334                    ScErrorType::Value,
335                    ScErrorCode::InvalidInput,
336                    "relative_to_absolute given an absolute reference",
337                    &[Val::from_u32(handle).to_val()],
338                ))
339            };
340        }
341        Ok(val)
342    }
343
344    pub fn absolute_to_relative(&self, val: Val) -> Result<Val, HostError> {
345        if let Ok(obj) = Object::try_from(val) {
346            let handle = obj.get_handle();
347            return if is_relative_object_handle(handle) {
348                // This gets "internal error" because we should never have found
349                // ourselves in posession of a relative reference to return to
350                // the user VM in the first place. Logic bug.
351                Err(self.err(
352                    ScErrorType::Context,
353                    ScErrorCode::InternalError,
354                    "absolute_to_relative given a relative reference",
355                    // NB: we convert to a U32Val here otherwise the _events_ system
356                    // will fault when trying to look up this argument as a relative
357                    // object reference.
358                    &[Val::from_u32(handle).to_val()],
359                ))
360            } else {
361                // Push a new entry into the relative-objects vector.
362                metered_clone::charge_heap_alloc::<Object>(1, self)?;
363                let index = self.with_current_frame_relative_object_table(|table| {
364                    let index = table.len();
365                    table.push(obj);
366                    Ok(index)
367                })?;
368                let handle = index_to_handle(self, index, true)?;
369                Ok(Object::from_handle_and_tag(handle, val.get_tag()).into())
370            };
371        }
372        Ok(val)
373    }
374
375    /// Moves a value of some type implementing [`HostObjectType`] into the
376    /// host's object array, returning the associated [`Object`] wrapper type
377    /// containing the new object's handle.
378    pub fn add_host_object<HOT: HostObjectType>(
379        &self,
380        hot: HOT,
381    ) -> Result<HOT::Wrapper, HostError> {
382        let _span = tracy_span!("add host object");
383        let index = self.try_borrow_objects()?.len();
384        let handle = index_to_handle(self, index, false)?;
385        // charge for the new host object, which is just the amortized cost of a
386        // single `HostObject` allocation
387        metered_clone::charge_heap_alloc::<HostObject>(1, self)?;
388        self.try_borrow_objects_mut()?.push(HOT::inject(hot));
389        Ok(HOT::new_from_handle(handle))
390    }
391
392    pub(crate) fn visit_obj_untyped<F, U>(
393        &self,
394        obj: impl Into<Object>,
395        f: F,
396    ) -> Result<U, HostError>
397    where
398        F: FnOnce(&HostObject) -> Result<U, HostError>,
399    {
400        let _span = tracy_span!("visit host object");
401        // `VisitObject` covers the cost of visiting an object. The actual cost
402        // of the closure needs to be covered by the caller. Although each visit
403        // does small amount of work -- getting the object handling and indexing
404        // into the host object buffer, almost too little to bother charging for
405        // -- it is ubiquitous and therefore we charge budget here for safety /
406        // future proofing.
407        self.charge_budget(ContractCostType::VisitObject, None)?;
408        let r = self.try_borrow_objects()?;
409        let obj: Object = obj.into();
410        let handle: u32 = obj.get_handle();
411        if is_relative_object_handle(handle) {
412            // This should never happen: we should have translated a relative
413            // object handle to an absolute before we got here.
414            Err(self.err(
415                ScErrorType::Object,
416                ScErrorCode::InternalError,
417                "looking up relative object",
418                &[Val::from_u32(handle).to_val()],
419            ))
420        } else if let Some(obj) = r.get(handle_to_index(handle)) {
421            f(obj)
422        } else {
423            // Discard the broken object here instead of including
424            // it in the error to avoid further attempts to interpret it.
425            // e.g. if diagnostics are on, then this would immediately
426            // begin recursing, attempting and failing to externalize
427            // debug info for this very error. Store the u64 payload instead.
428            let obj_payload = obj.as_val().get_payload();
429            let payload_val = Val::try_from_val(self, &obj_payload)?;
430            Err(self.err(
431                ScErrorType::Value,
432                ScErrorCode::InvalidInput,
433                "unknown object reference",
434                &[payload_val],
435            ))
436        }
437    }
438
439    // Notes on metering: object visiting part is covered by
440    // [`Host::visit_obj_untyped`]. Closure needs to be metered separately.
441    pub(crate) fn visit_obj<HOT: HostObjectType, F, U>(
442        &self,
443        obj: HOT::Wrapper,
444        f: F,
445    ) -> Result<U, HostError>
446    where
447        F: FnOnce(&HOT) -> Result<U, HostError>,
448    {
449        self.visit_obj_untyped(obj, |hobj| match HOT::try_extract(hobj) {
450            // This should never happen: we should have rejected a mis-tagged
451            // object handle before it got here.
452            None => Err(self.err(
453                xdr::ScErrorType::Object,
454                xdr::ScErrorCode::InternalError,
455                "object reference type does not match tag",
456                &[],
457            )),
458            Some(hot) => f(hot),
459        })
460    }
461}