Skip to content

Commit

Permalink
Merge pull request #517 from enigmampc/query-recursion-limit
Browse files Browse the repository at this point in the history
Added recursion limit of 5 to queries
  • Loading branch information
reuvenpo authored Sep 2, 2020
2 parents cdca776 + 59b3067 commit d4e3654
Show file tree
Hide file tree
Showing 27 changed files with 315 additions and 23 deletions.
2 changes: 2 additions & 0 deletions cosmwasm/packages/enclave-ffi-types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ pub enum EnclaveError {
Panic,
#[display(fmt = "enclave ran out of heap memory")]
OutOfMemory,
#[display(fmt = "depth of nested contract calls exceeded")]
ExceededRecursionLimit,
/// Unexpected Error happened, no more details available
#[display(fmt = "unknown error")]
Unknown,
Expand Down
2 changes: 2 additions & 0 deletions cosmwasm/packages/std/src/errors/system_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum SystemError {
NoSuchContract { addr: HumanAddr },
Unknown {},
UnsupportedRequest { kind: String },
ExceededRecursionLimit {},
}

impl std::error::Error for SystemError {}
Expand All @@ -45,6 +46,7 @@ impl std::fmt::Display for SystemError {
SystemError::UnsupportedRequest { kind } => {
write!(f, "Unsupported query type: {}", kind)
}
SystemError::ExceededRecursionLimit {} => write!(f, "Query recursion limit exceeded"),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cosmwasm/packages/wasmi-runtime/Enclave.config.prod.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x800000</StackMaxSize>
<HeapMaxSize>0x8000000</HeapMaxSize>
<HeapMaxSize>0x10000000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSPolicy>1</TCSPolicy>
<DisableDebug>1</DisableDebug>
Expand Down
2 changes: 1 addition & 1 deletion cosmwasm/packages/wasmi-runtime/Enclave.config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x800000</StackMaxSize>
<HeapMaxSize>0x4000000</HeapMaxSize>
<HeapMaxSize>0x10000000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSPolicy>1</TCSPolicy>
<DisableDebug>0</DisableDebug>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum SystemError {
NoSuchContract { addr: HumanAddr },
Unknown {},
UnsupportedRequest { kind: String },
ExceededRecursionLimit {},
}

pub type SystemResult<T> = Result<T, SystemError>;
41 changes: 40 additions & 1 deletion cosmwasm/packages/wasmi-runtime/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::results::{
result_query_success_to_queryresult,
};
use crate::{
oom_handler,
oom_handler, recursion_depth,
utils::{validate_const_ptr, validate_mut_ptr},
};

Expand Down Expand Up @@ -116,6 +116,19 @@ pub unsafe extern "C" fn ecall_init(
sig_info: *const u8,
sig_info_len: usize,
) -> InitResult {
let _recursion_guard = match recursion_depth::guard() {
Ok(rg) => rg,
Err(err) => {
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
// I believe that this error condition is currently unreachable.
// I think we can safely remove it completely right now, and have
// recursion_depth::increment() simply increment the counter with no further checks,
// but i wanted to stay on the safe side here, in case something changes in the
// future, and we can easily spot that we forgot to add a limit somewhere.
error!("recursion limit exceeded, can not perform init!");
return InitResult::Failure { err };
}
};
if let Err(err) = oom_handler::register_oom_handler() {
error!("Could not register OOM handler!");
return InitResult::Failure { err };
Expand Down Expand Up @@ -200,6 +213,19 @@ pub unsafe extern "C" fn ecall_handle(
sig_info: *const u8,
sig_info_len: usize,
) -> HandleResult {
let _recursion_guard = match recursion_depth::guard() {
Ok(rg) => rg,
Err(err) => {
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
// I believe that this error condition is currently unreachable.
// I think we can safely remove it completely right now, and have
// recursion_depth::increment() simply increment the counter with no further checks,
// but i wanted to stay on the safe side here, in case something changes in the
// future, and we can easily spot that we forgot to add a limit somewhere.
error!("recursion limit exceeded, can not perform handle!");
return HandleResult::Failure { err };
}
};
if let Err(err) = oom_handler::register_oom_handler() {
error!("Could not register OOM handler!");
return HandleResult::Failure { err };
Expand Down Expand Up @@ -280,6 +306,19 @@ pub unsafe extern "C" fn ecall_query(
msg: *const u8,
msg_len: usize,
) -> QueryResult {
let _recursion_guard = match recursion_depth::guard() {
Ok(rg) => rg,
Err(err) => {
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
// I believe that this error condition is currently unreachable.
// I think we can safely remove it completely right now, and have
// recursion_depth::increment() simply increment the counter with no further checks,
// but i wanted to stay on the safe side here, in case something changes in the
// future, and we can easily spot that we forgot to add a limit somewhere.
error!("recursion limit exceeded, can not perform query!");
return QueryResult::Failure { err };
}
};
if let Err(err) = oom_handler::register_oom_handler() {
error!("Could not register OOM handler!");
return QueryResult::Failure { err };
Expand Down
1 change: 1 addition & 0 deletions cosmwasm/packages/wasmi-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod exports;
pub mod imports;
pub mod logger;
mod oom_handler;
mod recursion_depth;
pub mod registration;
use std::env;

Expand Down
9 changes: 5 additions & 4 deletions cosmwasm/packages/wasmi-runtime/src/oom_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@ impl SafetyBuffer {
}

lazy_static! {
/// SAFETY_BUFFER is a 32 MiB of SafetyBuffer. This is occupying 50% of available memory
/// to be extra sure this is enough.
/// SAFETY_BUFFER is a 4 MiB of SafetyBuffer. This is twice the bare minimum to unwind after
/// a best-case OOM event. thanks to the recursion limit on queries, together with other memory
/// limits, we don't expect to hit OOM, and this mechanism remains in place just in case.
/// 2 MiB is the minimum allowed buffer. If we don't succeed to allocate 2 MiB, we throw a panic,
/// if we do succeed to allocate 2 MiB but less than 32 MiB than we move on and will try to allocate
/// if we do succeed to allocate 2 MiB but less than 4 MiB than we move on and will try to allocate
/// the rest on the next entry to the enclave.
static ref SAFETY_BUFFER: SgxMutex<SafetyBuffer> = SgxMutex::new(SafetyBuffer::new(16 * 1024, 2 * 1024));
static ref SAFETY_BUFFER: SgxMutex<SafetyBuffer> = SgxMutex::new(SafetyBuffer::new(4 * 1024, 2 * 1024));
}

static OOM_HAPPENED: AtomicBool = AtomicBool::new(false);
Expand Down
55 changes: 55 additions & 0 deletions cosmwasm/packages/wasmi-runtime/src/recursion_depth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::sync::SgxMutex;

use lazy_static::lazy_static;

use enclave_ffi_types::EnclaveError;

const RECURSION_LIMIT: u8 = 5;

lazy_static! {
/// This counter tracks the recursion depth of queries,
/// and effectively the amount of loaded instances of WASMI.
///
/// It is incremented before each computation begins and is decremented after each computation ends.
static ref RECURSION_DEPTH: SgxMutex<u8> = SgxMutex::new(0);
}

fn increment() -> Result<(), EnclaveError> {
let mut depth = RECURSION_DEPTH.lock().unwrap();
if *depth == RECURSION_LIMIT {
return Err(EnclaveError::ExceededRecursionLimit);
}
*depth = depth.saturating_add(1);
Ok(())
}

fn decrement() {
let mut depth = RECURSION_DEPTH.lock().unwrap();
*depth = depth.saturating_sub(1);
}

/// Returns whether or not this is the last possible level of recursion
pub fn limit_reached() -> bool {
*RECURSION_DEPTH.lock().unwrap() == RECURSION_LIMIT
}

pub struct RecursionGuard {
_private: (), // prevent direct instantiation outside this module
}

impl RecursionGuard {
pub fn new() -> Result<Self, EnclaveError> {
increment()?;
Ok(Self { _private: () })
}
}

impl Drop for RecursionGuard {
fn drop(&mut self) {
decrement();
}
}

pub fn guard() -> Result<RecursionGuard, EnclaveError> {
RecursionGuard::new()
}
43 changes: 30 additions & 13 deletions cosmwasm/packages/wasmi-runtime/src/wasm/query_chain.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use super::errors::WasmEngineError;
use crate::crypto::Ed25519PublicKey;
use crate::recursion_depth;
use crate::wasm::types::{IoNonce, SecretMessage};
use crate::{exports, imports};

use crate::cosmwasm::encoding::Binary;
use crate::cosmwasm::query::{QueryRequest, WasmQuery};
use crate::cosmwasm::{
encoding::Binary,
query::{QueryRequest, WasmQuery},
std_error::{StdError, StdResult},
system_error::{SystemError, SystemResult},
};
Expand All @@ -22,6 +23,10 @@ pub fn encrypt_and_query_chain(
gas_used: &mut u64,
gas_limit: u64,
) -> Result<Vec<u8>, WasmEngineError> {
if let Some(answer) = check_recursion_limit() {
return serialize_error_response(&answer);
}

let mut query_struct: QueryRequest = match serde_json::from_slice(query) {
Ok(query_struct) => query_struct,
Err(err) => {
Expand Down Expand Up @@ -182,6 +187,21 @@ fn query_chain(
(Ok(value), gas_used)
}

/// Check whether the query is allowed to run.
///
/// We make sure that a recursion limit is in place in order to
/// mitigate cases where the enclave runs out of memory.
fn check_recursion_limit() -> Option<SystemResult<StdResult<Binary>>> {
if recursion_depth::limit_reached() {
debug!(
"Recursion limit reached while performing nested queries. Returning error to contract."
);
Some(Err(SystemError::ExceededRecursionLimit {}))
} else {
None
}
}

fn system_error_invalid_request<T>(request: &[u8], err: T) -> Result<Vec<u8>, WasmEngineError>
where
T: std::fmt::Debug + ToString,
Expand All @@ -196,16 +216,7 @@ where
error: err.to_string(),
});

serde_json::to_vec(&answer).map_err(|err| {
// this should never happen
debug!(
"encrypt_and_query_chain() got an error while trying to serialize the error {:?} returned to WASM: {:?}",
answer,
err
);

WasmEngineError::SerializationError
})
serialize_error_response(&answer)
}

fn system_error_invalid_response<T>(response: Vec<u8>, err: T) -> Result<Vec<u8>, WasmEngineError>
Expand All @@ -217,7 +228,13 @@ where
error: err.to_string(),
});

serde_json::to_vec(&answer).map_err(|err| {
serialize_error_response(&answer)
}

fn serialize_error_response(
answer: &SystemResult<StdResult<Binary>>,
) -> Result<Vec<u8>, WasmEngineError> {
serde_json::to_vec(answer).map_err(|err| {
// this should never happen
debug!(
"encrypt_and_query_chain() got an error while trying to serialize the error {:?} returned to WASM: {:?}",
Expand Down
14 changes: 14 additions & 0 deletions go-cosmwasm/types/systemerror.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type SystemError struct {
NoSuchContract *NoSuchContract `json:"no_such_contract,omitempty"`
Unknown *Unknown `json:"unknown,omitempty"`
UnsupportedRequest *UnsupportedRequest `json:"unsupported_request,omitempty"`
ExceededRecursionLimit *ExceededRecursionLimit `json:"exceeded_recursion_limit,omitempty"`
}

var (
Expand All @@ -21,6 +22,7 @@ var (
_ error = NoSuchContract{}
_ error = Unknown{}
_ error = UnsupportedRequest{}
_ error = ExceededRecursionLimit{}
)

func (a SystemError) Error() string {
Expand All @@ -35,6 +37,8 @@ func (a SystemError) Error() string {
return a.Unknown.Error()
case a.UnsupportedRequest != nil:
return a.UnsupportedRequest.Error()
case a.ExceededRecursionLimit != nil:
return a.ExceededRecursionLimit.Error()
default:
panic("unknown error variant")
}
Expand Down Expand Up @@ -80,6 +84,12 @@ func (e UnsupportedRequest) Error() string {
return fmt.Sprintf("unsupported request: %s", e.Kind)
}

type ExceededRecursionLimit struct{}

func (e ExceededRecursionLimit) Error() string {
return "unknown system error"
}

// ToSystemError will try to convert the given error to an SystemError.
// This is important to returning any Go error back to Rust.
//
Expand Down Expand Up @@ -118,6 +128,10 @@ func ToSystemError(err error) *SystemError {
return &SystemError{UnsupportedRequest: &t}
case *UnsupportedRequest:
return &SystemError{UnsupportedRequest: t}
case ExceededRecursionLimit:
return &SystemError{ExceededRecursionLimit: &t}
case *ExceededRecursionLimit:
return &SystemError{ExceededRecursionLimit: t}
default:
return nil
}
Expand Down
27 changes: 25 additions & 2 deletions x/compute/internal/keeper/recurse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
expectedGas uint64
expectOutOfGas bool
expectOOM bool
expectRecursionLimit bool
}{
"no recursion, lots of work": {
gasLimit: 4_000_000,
Expand All @@ -291,6 +292,18 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
expectQueriesFromContract: 0,
expectedGas: GasWork2k,
},
"recursion 4, lots of work": {
gasLimit: 4_000_000,
msg: Recurse{
Depth: 4,
Work: 2000,
},
expectQueriesFromContract: 4,
expectedGas: GasWork2k + 9*(GasWork2k+GasReturnHashed),
expectOutOfGas: false,
expectOOM: false,
expectRecursionLimit: false,
},
"recursion 9, lots of work": {
gasLimit: 4_000_000,
msg: Recurse{
Expand All @@ -300,7 +313,8 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
expectQueriesFromContract: 9,
expectedGas: GasWork2k + 9*(GasWork2k+GasReturnHashed),
expectOutOfGas: false,
expectOOM: true,
expectOOM: false,
expectRecursionLimit: true,
},
// this is where we expect an error...
// it has enough gas to run 4 times and die on the 5th (4th time dispatching to sub-contract)
Expand All @@ -314,7 +328,8 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
},
expectQueriesFromContract: 4,
expectOutOfGas: false,
expectOOM: true,
expectOOM: false,
expectRecursionLimit: true,
},
}

Expand Down Expand Up @@ -354,6 +369,14 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
return
}

if tc.expectRecursionLimit {
_, qErr := queryHelper(t, keeper, ctx, contractAddr, string(msg), true, tc.gasLimit)
require.NotEmpty(t, qErr)
require.NotNil(t, qErr.GenericErr)
require.Contains(t, qErr.GenericErr.Msg, "Querier system error: Query recursion limit exceeded")
return
}

// otherwise, we expect a successful call
_, qErr := queryHelper(t, keeper, ctx, contractAddr, string(msg), true, tc.gasLimit)
require.Empty(t, qErr)
Expand Down
Loading

0 comments on commit d4e3654

Please sign in to comment.