Skip to content

Commit

Permalink
Host function protocol gating (stellar#1348)
Browse files Browse the repository at this point in the history
### What

Allow host functions to specify its supported protocol bounds, e.g. 

```diff
        {
            "name": "test",
            "export": "t",
            "functions": [
                {
                    "export": "_",
                    "name": "dummy0",
                    "args": [],
                    "return": "Val",
                    "docs": "A dummy function taking 0 arguments and performs no-op.                
+                  "min_supported_protocol": 20,
+                   "max_supported_protocol": 21
                }                
            ]
        },
```

And adds logic (mostly macro generated) that automatically checks and
enforces these protocol bounds in various paths:
- Host running as native. Covered by `impl Env for VmCallerEnv`.
- Host running from the guest Wasm contract. Covered by Vm link-time
check, as well as the dispatch functions (as a redundant safe guard).

Tests are added in `tests/protocol_gate.rs`.

### Why

[TODO: Why this change is being made. Include any context required to
understand the why.]

### Known limitations

[TODO or N/A]

---------

Co-authored-by: Graydon Hoare <[email protected]>
  • Loading branch information
jayz22 and graydon authored Feb 26, 2024
1 parent f035dda commit abd977d
Show file tree
Hide file tree
Showing 22 changed files with 601 additions and 52 deletions.
11 changes: 10 additions & 1 deletion soroban-env-common/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,16 @@
"args": [],
"return": "Val",
"docs": "A dummy function taking 0 arguments and performs no-op. This function is for test purpose only, for measuring the roundtrip cost of invoking a host function, i.e. host->Vm->host."
}
},
{
"export": "0",
"name": "protocol_gated_dummy",
"args": [],
"return": "Val",
"docs": "A dummy function for testing the protocol gating. Takes 0 arguments and performs no-op",
"min_supported_protocol": 19,
"max_supported_protocol": 19
}
]
},
{
Expand Down
13 changes: 11 additions & 2 deletions soroban-env-common/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ pub trait EnvBase: Sized + Clone {
/// events are enabled. When running on host, logs directly; when running on
/// guest, redirects through log_from_linear_memory.
fn log_from_slice(&self, msg: &str, vals: &[Val]) -> Result<Void, Self::Error>;

/// Check the current ledger protocol version against a provided lower
/// bound, error if protocol version is out-of-bound.
fn check_protocol_version_lower_bound(&self, lower_bound: u32) -> Result<(), Self::Error>;

/// Check the current ledger protocol version against a provided upper
/// bound, error if protocol version is out-of-bound.
fn check_protocol_version_upper_bound(&self, upper_bound: u32) -> Result<(), Self::Error>;
}

/// This trait is used by macro-generated dispatch and forwarding functions to
Expand Down Expand Up @@ -329,6 +337,7 @@ generate_call_macro_with_all_host_functions!("env.json");
// trait.
macro_rules! host_function_helper {
{
$($min_proto:literal)?, $($max_proto:literal)?,
$(#[$attr:meta])*
fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
=>
Expand Down Expand Up @@ -359,7 +368,7 @@ macro_rules! generate_env_trait {
// pattern-repetition matcher so that it will match all such
// descriptions.
$(#[$fn_attr:meta])*
{ $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
{ $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
)*
}
)*
Expand Down Expand Up @@ -387,7 +396,7 @@ macro_rules! generate_env_trait {
// block repetition-level from the outer pattern in the
// expansion, flattening all functions from all 'mod' blocks
// into the Env trait.
host_function_helper!{$(#[$fn_attr])* fn $fn_id $args -> $ret}
host_function_helper!{$($min_proto)?, $($max_proto)?, $(#[$fn_attr])* fn $fn_id $args -> $ret}
)*
)*
}
Expand Down
18 changes: 13 additions & 5 deletions soroban-env-common/src/vmcaller_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl<'a, T> VmCaller<'a, T> {
// trait.
macro_rules! host_function_helper {
{
$($min_proto:literal)?, $($max_proto:literal)?,
$(#[$attr:meta])*
fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty
}
Expand Down Expand Up @@ -109,7 +110,7 @@ macro_rules! generate_vmcaller_checked_env_trait {
// pattern-repetition matcher so that it will match all such
// descriptions.
$(#[$fn_attr:meta])*
{ $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
{ $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
)*
}
)*
Expand Down Expand Up @@ -146,7 +147,7 @@ macro_rules! generate_vmcaller_checked_env_trait {
// block repetition-level from the outer pattern in the
// expansion, flattening all functions from all 'mod' blocks
// into the VmCallerEnv trait.
host_function_helper!{$(#[$fn_attr])* fn $fn_id $args -> $ret}
host_function_helper!{$($min_proto)?, $($max_proto)?, $(#[$fn_attr])* fn $fn_id $args -> $ret}
)*
)*
}
Expand All @@ -169,14 +170,21 @@ call_macro_with_all_host_functions! { generate_vmcaller_checked_env_trait }
// and produces the the corresponding method declaration to be used in the Env
// trait.
macro_rules! vmcaller_none_function_helper {
{fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
{$($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
=>
{
// We call `augment_err_result` here to give the Env a chance to attach
// context (eg. a backtrace) to any error that was generated by code
// that didn't have an Env on hand when creating the error. This will at
// least localize the error to a given Env call.
fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
// Check the ledger protocol version against the function-specified
// boundaries, this prevents calling a host function using the host
// directly as `Env` (i.e. native mode) when the protocol version is
// out of bound.
$( self.check_protocol_version_lower_bound($min_proto)?; )?
$( self.check_protocol_version_upper_bound($max_proto)?; )?

#[cfg(all(not(target_family = "wasm"), feature = "tracy"))]
let _span = tracy_span!(core::stringify!($fn_id));
#[cfg(feature = "std")]
Expand Down Expand Up @@ -224,7 +232,7 @@ macro_rules! impl_env_for_vmcaller_env {
// pattern-repetition matcher so that it will match all such
// descriptions.
$(#[$fn_attr:meta])*
{ $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
{ $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
)*
}
)*
Expand All @@ -249,7 +257,7 @@ macro_rules! impl_env_for_vmcaller_env {
// block repetition-level from the outer pattern in the
// expansion, flattening all functions from all 'mod' blocks
// into the impl.
vmcaller_none_function_helper!{fn $fn_id $args -> $ret}
vmcaller_none_function_helper!{$($min_proto)?, $($max_proto)?, fn $fn_id $args -> $ret}
)*
)*
}
Expand Down
18 changes: 14 additions & 4 deletions soroban-env-guest/src/guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ impl EnvBase for Guest {
let vals_lm_len: U32Val = Val::from_u32(vals.len() as u32);
self.log_from_linear_memory(msg_lm_pos, msg_lm_len, vals_lm_pos, vals_lm_len)
}

fn check_protocol_version_lower_bound(&self, _lower_bound: u32) -> Result<(), Self::Error> {
core::arch::wasm32::unreachable()
}

fn check_protocol_version_upper_bound(&self, _upper_bound: u32) -> Result<(), Self::Error> {
core::arch::wasm32::unreachable()
}
}

///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -228,7 +236,7 @@ macro_rules! impl_env_for_guest {
// pattern-repetition matcher so that it will match all such
// descriptions.
$(#[$fn_attr:meta])*
{ $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
{ $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
)*
}
)*
Expand Down Expand Up @@ -276,7 +284,9 @@ call_macro_with_all_host_functions! { impl_env_for_guest }
// unsafe extern function).
macro_rules! extern_function_helper {
{
$fn_str:literal, $(#[$attr:meta])* fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty
$fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?,
$(#[$attr:meta])*
fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty
}
=>
{
Expand Down Expand Up @@ -307,7 +317,7 @@ macro_rules! generate_extern_modules {
// pattern-repetition matcher so that it will match all such
// descriptions.
$(#[$fn_attr:meta])*
{ $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
{ $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
)*
}
)*
Expand Down Expand Up @@ -341,7 +351,7 @@ macro_rules! generate_extern_modules {
// one `$()*` pattern-repetition expander so that it
// repeats only for the part of each mod that the
// corresponding pattern-repetition matcher.
extern_function_helper!{$fn_str, $(#[$fn_attr])* fn $fn_id $args -> $ret}
extern_function_helper!{$fn_str, $($min_proto)?, $($max_proto)?, $(#[$fn_attr])* fn $fn_id $args -> $ret}
)*
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
" 11 call upload_wasm(Bytes(obj#5))": "",
" 12 ret upload_wasm -> Err(Error(WasmVm, InvalidInput))": "cpu:1402288, mem:393618",
" 13 call bytes_new_from_slice(40)": "cpu:1402335",
" 14 ret bytes_new_from_slice -> Ok(Bytes(obj#7))": "cpu:1403306, mem:393738, objs:-/4@c897768d",
" 14 ret bytes_new_from_slice -> Ok(Bytes(obj#7))": "cpu:1403306, mem:393738, objs:-/4@523eb858",
" 15 call upload_wasm(Bytes(obj#7))": "",
" 16 ret upload_wasm -> Err(Error(WasmVm, InvalidInput))": "cpu:1876278, mem:525489",
" 17 call bytes_new_from_slice(40)": "cpu:1876325",
" 18 ret bytes_new_from_slice -> Ok(Bytes(obj#9))": "cpu:1877296, mem:525609, objs:-/5@685ce86b",
" 18 ret bytes_new_from_slice -> Ok(Bytes(obj#9))": "cpu:1877296, mem:525609, objs:-/5@a388daeb",
" 19 call upload_wasm(Bytes(obj#9))": "",
" 20 ret upload_wasm -> Err(Error(WasmVm, InvalidInput))": "cpu:2350268, mem:657360",
" 21 call bytes_new_from_slice(40)": "cpu:2350315",
" 22 ret bytes_new_from_slice -> Ok(Bytes(obj#11))": "cpu:2351286, mem:657480, objs:-/6@b8b5123",
" 22 ret bytes_new_from_slice -> Ok(Bytes(obj#11))": "cpu:2351286, mem:657480, objs:-/6@6edee4ba",
" 23 call upload_wasm(Bytes(obj#11))": "",
" 24 ret upload_wasm -> Err(Error(WasmVm, InvalidInput))": "cpu:2824258, mem:789231",
" 25 end": "cpu:2824258, mem:789231, prngs:-/9b4a753, objs:-/6@b8b5123, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
" 25 end": "cpu:2824258, mem:789231, prngs:-/9b4a753, objs:-/6@6edee4ba, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
" 0 begin": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-",
" 1 call bytes_new_from_slice(80)": "cpu:14535",
" 2 ret bytes_new_from_slice -> Ok(Bytes(obj#1))": "cpu:15516, mem:160, objs:-/1@6105f6e8",
" 3 call upload_wasm(Bytes(obj#1))": "",
" 4 ret upload_wasm -> Err(Error(Context, InternalError))": "cpu:504878, mem:133534",
" 5 end": "cpu:504878, mem:133534, prngs:-/9b4a753, objs:-/1@6105f6e8, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
" 0 begin": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-",
" 1 call protocol_gated_dummy()": "",
" 2 ret protocol_gated_dummy -> Ok(Void)": "cpu:14810",
" 3 end": "cpu:14810, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
" 0 begin": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-",
" 1 call bytes_new_from_slice(80)": "cpu:14535",
" 2 ret bytes_new_from_slice -> Ok(Bytes(obj#1))": "cpu:15516, mem:160, objs:-/1@f7fca391",
" 3 call upload_wasm(Bytes(obj#1))": "",
" 4 ret upload_wasm -> Err(Error(WasmVm, InvalidAction))": "cpu:504878, mem:133534",
" 5 call bytes_new_from_slice(80)": "cpu:504925",
" 6 ret bytes_new_from_slice -> Ok(Bytes(obj#3))": "cpu:505906, mem:133694, objs:-/2@3906a726",
" 7 call upload_wasm(Bytes(obj#3))": "",
" 8 ret upload_wasm -> Err(Error(WasmVm, InvalidAction))": "cpu:995268, mem:267068",
" 9 call bytes_new_from_slice(80)": "cpu:995315",
" 10 ret bytes_new_from_slice -> Ok(Bytes(obj#5))": "cpu:996296, mem:267228, objs:-/3@e4dbdc61",
" 11 call upload_wasm(Bytes(obj#5))": "",
" 12 ret upload_wasm -> Ok(Bytes(obj#7))": "cpu:1490848, mem:401372, objs:-/4@4ac3bfa9, store:-/1@1a0e9b81, foot:1@528fd006",
" 13 call bytes_new_from_slice(32)": "cpu:1491288, mem:401436, objs:-/5@6a4850f0",
" 14 ret bytes_new_from_slice -> Ok(Bytes(obj#11))": "cpu:1492257, mem:401548, objs:-/6@dc239eb6",
" 15 call create_contract(Address(obj#9), Bytes(obj#7), Bytes(obj#11))": "",
" 16 call obj_cmp(Address(obj#13), Address(obj#9))": "cpu:1493900, mem:401726, objs:-/7@cb0ab20f, auth:1@9f16c057/-",
" 17 ret obj_cmp -> Ok(0)": "cpu:1494192",
" 18 call get_ledger_network_id()": "cpu:1494242, auth:1@9f16c057/1@82b76fa7",
" 19 ret get_ledger_network_id -> Ok(Bytes(obj#15))": "cpu:1495272, mem:401838, objs:-/8@e6d133a3",
" 20 ret create_contract -> Ok(Address(obj#17))": "cpu:1513405, mem:404912, objs:-/9@992d388f, store:-/2@dc3a5709, foot:2@ed32f193, auth:-/1@aebeac48",
" 21 call call(Address(obj#17), Symbol(test), Vec(obj#19))": "cpu:1514366, mem:404992, objs:-/10@9c15842c, auth:-/-",
" 22 push VM:a98ace28:test()": "cpu:2006415, mem:539598, objs:-/11@3b5daa87, vm:-/1@32eec24c, stk:1@9ed5379c, auth:1@b1b428e/-",
" 23 call protocol_gated_dummy()": "cpu:2008884, mem:539628, vm:-/-",
" 24 ret protocol_gated_dummy -> Ok(Void)": "cpu:2009470",
" 25 pop VM:a98ace28:test -> Ok(Void)": " vm:-/1@32eec24c",
" 26 ret call -> Ok(Void)": " vm:-/-, stk:-, auth:-/-",
" 27 call bytes_new_from_slice(80)": "cpu:2009517",
" 28 ret bytes_new_from_slice -> Ok(Bytes(obj#23))": "cpu:2010498, mem:539788, objs:-/12@fbaeb755",
" 29 call upload_wasm(Bytes(obj#23))": "",
" 30 ret upload_wasm -> Err(Error(WasmVm, InvalidAction))": "cpu:2499860, mem:673162",
" 31 call bytes_new_from_slice(80)": "cpu:2499907",
" 32 ret bytes_new_from_slice -> Ok(Bytes(obj#25))": "cpu:2500888, mem:673322, objs:-/13@aa6c2723",
" 33 call upload_wasm(Bytes(obj#25))": "",
" 34 ret upload_wasm -> Err(Error(WasmVm, InvalidAction))": "cpu:2990250, mem:806696",
" 35 end": "cpu:2990250, mem:806696, prngs:-/9b4a753, objs:-/13@aa6c2723, vm:-/-, evt:-, store:-/2@dc3a5709, foot:2@ed32f193, stk:-, auth:-/-"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
" 0 begin": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-",
" 1 call protocol_gated_dummy()": "",
" 2 ret protocol_gated_dummy -> Ok(Void)": "",
" 3 end": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
" 0 begin": "cpu:14488, mem:0, prngs:-/9b4a753, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-",
" 1 call bytes_new_from_slice(80)": "cpu:14535",
" 2 ret bytes_new_from_slice -> Ok(Bytes(obj#1))": "cpu:15516, mem:160, objs:-/1@6105f6e8",
" 3 call upload_wasm(Bytes(obj#1))": "",
" 4 ret upload_wasm -> Err(Error(WasmVm, InvalidInput))": "cpu:504878, mem:133534",
" 5 end": "cpu:504878, mem:133534, prngs:-/9b4a753, objs:-/1@6105f6e8, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-"
}
1 change: 1 addition & 0 deletions soroban-env-host/src/budget/dimension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{Error, HostError};
use core::fmt::Debug;

/// Helper types to annotate boolean function arguments
#[allow(dead_code)]
pub(crate) struct IsCpu(pub(crate) bool);
pub(crate) struct IsShadowMode(pub(crate) bool);

Expand Down
39 changes: 39 additions & 0 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,38 @@ impl EnvBase for Host {
call_trace_env_ret!(self, res);
res
}

fn check_protocol_version_lower_bound(&self, lower: u32) -> Result<(), Self::Error> {
self.with_ledger_info(|li| {
let proto = li.protocol_version;
if proto < lower {
Err(self.err(
ScErrorType::Context,
ScErrorCode::IndexBounds,
"ledger protocol {} is less than specified lower bound {}",
&[Val::from_u32(proto).into(), Val::from_u32(lower).into()],
))
} else {
Ok(())
}
})
}

fn check_protocol_version_upper_bound(&self, upper: u32) -> Result<(), Self::Error> {
self.with_ledger_info(|li| {
let proto = li.protocol_version;
if proto > upper {
Err(self.err(
ScErrorType::Context,
ScErrorCode::IndexBounds,
"ledger protocol {} is larger than specified upper bound {}",
&[Val::from_u32(proto).into(), Val::from_u32(upper).into()],
))
} else {
Ok(())
}
})
}
}

impl VmCallerEnv for Host {
Expand Down Expand Up @@ -2706,6 +2738,13 @@ impl VmCallerEnv for Host {
Ok(().into())
}

fn protocol_gated_dummy(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
) -> Result<Val, Self::Error> {
Ok(().into())
}

// endregion: "test" module functions
// region: "address" module functions

Expand Down
1 change: 1 addition & 0 deletions soroban-env-host/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod metering_benchmark;
mod num;
mod post_mvp;
mod prng;
mod protocol_gate;
mod stellar_asset_contract;
mod storage;
mod str;
Expand Down
Loading

0 comments on commit abd977d

Please sign in to comment.