Skip to content

Commit d4e3654

Browse files
authored
Merge pull request #517 from enigmampc/query-recursion-limit
Added recursion limit of 5 to queries
2 parents cdca776 + 59b3067 commit d4e3654

27 files changed

+315
-23
lines changed

cosmwasm/packages/enclave-ffi-types/src/types.rs

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ pub enum EnclaveError {
154154
Panic,
155155
#[display(fmt = "enclave ran out of heap memory")]
156156
OutOfMemory,
157+
#[display(fmt = "depth of nested contract calls exceeded")]
158+
ExceededRecursionLimit,
157159
/// Unexpected Error happened, no more details available
158160
#[display(fmt = "unknown error")]
159161
Unknown,

cosmwasm/packages/std/src/errors/system_error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub enum SystemError {
2121
NoSuchContract { addr: HumanAddr },
2222
Unknown {},
2323
UnsupportedRequest { kind: String },
24+
ExceededRecursionLimit {},
2425
}
2526

2627
impl std::error::Error for SystemError {}
@@ -45,6 +46,7 @@ impl std::fmt::Display for SystemError {
4546
SystemError::UnsupportedRequest { kind } => {
4647
write!(f, "Unsupported query type: {}", kind)
4748
}
49+
SystemError::ExceededRecursionLimit {} => write!(f, "Query recursion limit exceeded"),
4850
}
4951
}
5052
}

cosmwasm/packages/wasmi-runtime/Enclave.config.prod.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ProdID>0</ProdID>
44
<ISVSVN>0</ISVSVN>
55
<StackMaxSize>0x800000</StackMaxSize>
6-
<HeapMaxSize>0x8000000</HeapMaxSize>
6+
<HeapMaxSize>0x10000000</HeapMaxSize>
77
<TCSNum>1</TCSNum>
88
<TCSPolicy>1</TCSPolicy>
99
<DisableDebug>1</DisableDebug>

cosmwasm/packages/wasmi-runtime/Enclave.config.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ProdID>0</ProdID>
44
<ISVSVN>0</ISVSVN>
55
<StackMaxSize>0x800000</StackMaxSize>
6-
<HeapMaxSize>0x4000000</HeapMaxSize>
6+
<HeapMaxSize>0x10000000</HeapMaxSize>
77
<TCSNum>1</TCSNum>
88
<TCSPolicy>1</TCSPolicy>
99
<DisableDebug>0</DisableDebug>

cosmwasm/packages/wasmi-runtime/src/cosmwasm/system_error.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub enum SystemError {
2323
NoSuchContract { addr: HumanAddr },
2424
Unknown {},
2525
UnsupportedRequest { kind: String },
26+
ExceededRecursionLimit {},
2627
}
2728

2829
pub type SystemResult<T> = Result<T, SystemError>;

cosmwasm/packages/wasmi-runtime/src/exports.rs

+40-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::results::{
1313
result_query_success_to_queryresult,
1414
};
1515
use crate::{
16-
oom_handler,
16+
oom_handler, recursion_depth,
1717
utils::{validate_const_ptr, validate_mut_ptr},
1818
};
1919

@@ -116,6 +116,19 @@ pub unsafe extern "C" fn ecall_init(
116116
sig_info: *const u8,
117117
sig_info_len: usize,
118118
) -> InitResult {
119+
let _recursion_guard = match recursion_depth::guard() {
120+
Ok(rg) => rg,
121+
Err(err) => {
122+
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
123+
// I believe that this error condition is currently unreachable.
124+
// I think we can safely remove it completely right now, and have
125+
// recursion_depth::increment() simply increment the counter with no further checks,
126+
// but i wanted to stay on the safe side here, in case something changes in the
127+
// future, and we can easily spot that we forgot to add a limit somewhere.
128+
error!("recursion limit exceeded, can not perform init!");
129+
return InitResult::Failure { err };
130+
}
131+
};
119132
if let Err(err) = oom_handler::register_oom_handler() {
120133
error!("Could not register OOM handler!");
121134
return InitResult::Failure { err };
@@ -200,6 +213,19 @@ pub unsafe extern "C" fn ecall_handle(
200213
sig_info: *const u8,
201214
sig_info_len: usize,
202215
) -> HandleResult {
216+
let _recursion_guard = match recursion_depth::guard() {
217+
Ok(rg) => rg,
218+
Err(err) => {
219+
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
220+
// I believe that this error condition is currently unreachable.
221+
// I think we can safely remove it completely right now, and have
222+
// recursion_depth::increment() simply increment the counter with no further checks,
223+
// but i wanted to stay on the safe side here, in case something changes in the
224+
// future, and we can easily spot that we forgot to add a limit somewhere.
225+
error!("recursion limit exceeded, can not perform handle!");
226+
return HandleResult::Failure { err };
227+
}
228+
};
203229
if let Err(err) = oom_handler::register_oom_handler() {
204230
error!("Could not register OOM handler!");
205231
return HandleResult::Failure { err };
@@ -280,6 +306,19 @@ pub unsafe extern "C" fn ecall_query(
280306
msg: *const u8,
281307
msg_len: usize,
282308
) -> QueryResult {
309+
let _recursion_guard = match recursion_depth::guard() {
310+
Ok(rg) => rg,
311+
Err(err) => {
312+
// https://github.com/enigmampc/SecretNetwork/pull/517#discussion_r481924571
313+
// I believe that this error condition is currently unreachable.
314+
// I think we can safely remove it completely right now, and have
315+
// recursion_depth::increment() simply increment the counter with no further checks,
316+
// but i wanted to stay on the safe side here, in case something changes in the
317+
// future, and we can easily spot that we forgot to add a limit somewhere.
318+
error!("recursion limit exceeded, can not perform query!");
319+
return QueryResult::Failure { err };
320+
}
321+
};
283322
if let Err(err) = oom_handler::register_oom_handler() {
284323
error!("Could not register OOM handler!");
285324
return QueryResult::Failure { err };

cosmwasm/packages/wasmi-runtime/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod exports;
2121
pub mod imports;
2222
pub mod logger;
2323
mod oom_handler;
24+
mod recursion_depth;
2425
pub mod registration;
2526
use std::env;
2627

cosmwasm/packages/wasmi-runtime/src/oom_handler.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,13 @@ impl SafetyBuffer {
7272
}
7373

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

8384
static OOM_HAPPENED: AtomicBool = AtomicBool::new(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::sync::SgxMutex;
2+
3+
use lazy_static::lazy_static;
4+
5+
use enclave_ffi_types::EnclaveError;
6+
7+
const RECURSION_LIMIT: u8 = 5;
8+
9+
lazy_static! {
10+
/// This counter tracks the recursion depth of queries,
11+
/// and effectively the amount of loaded instances of WASMI.
12+
///
13+
/// It is incremented before each computation begins and is decremented after each computation ends.
14+
static ref RECURSION_DEPTH: SgxMutex<u8> = SgxMutex::new(0);
15+
}
16+
17+
fn increment() -> Result<(), EnclaveError> {
18+
let mut depth = RECURSION_DEPTH.lock().unwrap();
19+
if *depth == RECURSION_LIMIT {
20+
return Err(EnclaveError::ExceededRecursionLimit);
21+
}
22+
*depth = depth.saturating_add(1);
23+
Ok(())
24+
}
25+
26+
fn decrement() {
27+
let mut depth = RECURSION_DEPTH.lock().unwrap();
28+
*depth = depth.saturating_sub(1);
29+
}
30+
31+
/// Returns whether or not this is the last possible level of recursion
32+
pub fn limit_reached() -> bool {
33+
*RECURSION_DEPTH.lock().unwrap() == RECURSION_LIMIT
34+
}
35+
36+
pub struct RecursionGuard {
37+
_private: (), // prevent direct instantiation outside this module
38+
}
39+
40+
impl RecursionGuard {
41+
pub fn new() -> Result<Self, EnclaveError> {
42+
increment()?;
43+
Ok(Self { _private: () })
44+
}
45+
}
46+
47+
impl Drop for RecursionGuard {
48+
fn drop(&mut self) {
49+
decrement();
50+
}
51+
}
52+
53+
pub fn guard() -> Result<RecursionGuard, EnclaveError> {
54+
RecursionGuard::new()
55+
}

cosmwasm/packages/wasmi-runtime/src/wasm/query_chain.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use super::errors::WasmEngineError;
22
use crate::crypto::Ed25519PublicKey;
3+
use crate::recursion_depth;
34
use crate::wasm::types::{IoNonce, SecretMessage};
45
use crate::{exports, imports};
56

6-
use crate::cosmwasm::encoding::Binary;
7-
use crate::cosmwasm::query::{QueryRequest, WasmQuery};
87
use crate::cosmwasm::{
8+
encoding::Binary,
9+
query::{QueryRequest, WasmQuery},
910
std_error::{StdError, StdResult},
1011
system_error::{SystemError, SystemResult},
1112
};
@@ -22,6 +23,10 @@ pub fn encrypt_and_query_chain(
2223
gas_used: &mut u64,
2324
gas_limit: u64,
2425
) -> Result<Vec<u8>, WasmEngineError> {
26+
if let Some(answer) = check_recursion_limit() {
27+
return serialize_error_response(&answer);
28+
}
29+
2530
let mut query_struct: QueryRequest = match serde_json::from_slice(query) {
2631
Ok(query_struct) => query_struct,
2732
Err(err) => {
@@ -182,6 +187,21 @@ fn query_chain(
182187
(Ok(value), gas_used)
183188
}
184189

190+
/// Check whether the query is allowed to run.
191+
///
192+
/// We make sure that a recursion limit is in place in order to
193+
/// mitigate cases where the enclave runs out of memory.
194+
fn check_recursion_limit() -> Option<SystemResult<StdResult<Binary>>> {
195+
if recursion_depth::limit_reached() {
196+
debug!(
197+
"Recursion limit reached while performing nested queries. Returning error to contract."
198+
);
199+
Some(Err(SystemError::ExceededRecursionLimit {}))
200+
} else {
201+
None
202+
}
203+
}
204+
185205
fn system_error_invalid_request<T>(request: &[u8], err: T) -> Result<Vec<u8>, WasmEngineError>
186206
where
187207
T: std::fmt::Debug + ToString,
@@ -196,16 +216,7 @@ where
196216
error: err.to_string(),
197217
});
198218

199-
serde_json::to_vec(&answer).map_err(|err| {
200-
// this should never happen
201-
debug!(
202-
"encrypt_and_query_chain() got an error while trying to serialize the error {:?} returned to WASM: {:?}",
203-
answer,
204-
err
205-
);
206-
207-
WasmEngineError::SerializationError
208-
})
219+
serialize_error_response(&answer)
209220
}
210221

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

220-
serde_json::to_vec(&answer).map_err(|err| {
231+
serialize_error_response(&answer)
232+
}
233+
234+
fn serialize_error_response(
235+
answer: &SystemResult<StdResult<Binary>>,
236+
) -> Result<Vec<u8>, WasmEngineError> {
237+
serde_json::to_vec(answer).map_err(|err| {
221238
// this should never happen
222239
debug!(
223240
"encrypt_and_query_chain() got an error while trying to serialize the error {:?} returned to WASM: {:?}",

go-cosmwasm/types/systemerror.go

+14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type SystemError struct {
1212
NoSuchContract *NoSuchContract `json:"no_such_contract,omitempty"`
1313
Unknown *Unknown `json:"unknown,omitempty"`
1414
UnsupportedRequest *UnsupportedRequest `json:"unsupported_request,omitempty"`
15+
ExceededRecursionLimit *ExceededRecursionLimit `json:"exceeded_recursion_limit,omitempty"`
1516
}
1617

1718
var (
@@ -21,6 +22,7 @@ var (
2122
_ error = NoSuchContract{}
2223
_ error = Unknown{}
2324
_ error = UnsupportedRequest{}
25+
_ error = ExceededRecursionLimit{}
2426
)
2527

2628
func (a SystemError) Error() string {
@@ -35,6 +37,8 @@ func (a SystemError) Error() string {
3537
return a.Unknown.Error()
3638
case a.UnsupportedRequest != nil:
3739
return a.UnsupportedRequest.Error()
40+
case a.ExceededRecursionLimit != nil:
41+
return a.ExceededRecursionLimit.Error()
3842
default:
3943
panic("unknown error variant")
4044
}
@@ -80,6 +84,12 @@ func (e UnsupportedRequest) Error() string {
8084
return fmt.Sprintf("unsupported request: %s", e.Kind)
8185
}
8286

87+
type ExceededRecursionLimit struct{}
88+
89+
func (e ExceededRecursionLimit) Error() string {
90+
return "unknown system error"
91+
}
92+
8393
// ToSystemError will try to convert the given error to an SystemError.
8494
// This is important to returning any Go error back to Rust.
8595
//
@@ -118,6 +128,10 @@ func ToSystemError(err error) *SystemError {
118128
return &SystemError{UnsupportedRequest: &t}
119129
case *UnsupportedRequest:
120130
return &SystemError{UnsupportedRequest: t}
131+
case ExceededRecursionLimit:
132+
return &SystemError{ExceededRecursionLimit: &t}
133+
case *ExceededRecursionLimit:
134+
return &SystemError{ExceededRecursionLimit: t}
121135
default:
122136
return nil
123137
}

x/compute/internal/keeper/recurse_test.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
281281
expectedGas uint64
282282
expectOutOfGas bool
283283
expectOOM bool
284+
expectRecursionLimit bool
284285
}{
285286
"no recursion, lots of work": {
286287
gasLimit: 4_000_000,
@@ -291,6 +292,18 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
291292
expectQueriesFromContract: 0,
292293
expectedGas: GasWork2k,
293294
},
295+
"recursion 4, lots of work": {
296+
gasLimit: 4_000_000,
297+
msg: Recurse{
298+
Depth: 4,
299+
Work: 2000,
300+
},
301+
expectQueriesFromContract: 4,
302+
expectedGas: GasWork2k + 9*(GasWork2k+GasReturnHashed),
303+
expectOutOfGas: false,
304+
expectOOM: false,
305+
expectRecursionLimit: false,
306+
},
294307
"recursion 9, lots of work": {
295308
gasLimit: 4_000_000,
296309
msg: Recurse{
@@ -300,7 +313,8 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
300313
expectQueriesFromContract: 9,
301314
expectedGas: GasWork2k + 9*(GasWork2k+GasReturnHashed),
302315
expectOutOfGas: false,
303-
expectOOM: true,
316+
expectOOM: false,
317+
expectRecursionLimit: true,
304318
},
305319
// this is where we expect an error...
306320
// it has enough gas to run 4 times and die on the 5th (4th time dispatching to sub-contract)
@@ -314,7 +328,8 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
314328
},
315329
expectQueriesFromContract: 4,
316330
expectOutOfGas: false,
317-
expectOOM: true,
331+
expectOOM: false,
332+
expectRecursionLimit: true,
318333
},
319334
}
320335

@@ -354,6 +369,14 @@ func TestLimitRecursiveQueryGas(t *testing.T) {
354369
return
355370
}
356371

372+
if tc.expectRecursionLimit {
373+
_, qErr := queryHelper(t, keeper, ctx, contractAddr, string(msg), true, tc.gasLimit)
374+
require.NotEmpty(t, qErr)
375+
require.NotNil(t, qErr.GenericErr)
376+
require.Contains(t, qErr.GenericErr.Msg, "Querier system error: Query recursion limit exceeded")
377+
return
378+
}
379+
357380
// otherwise, we expect a successful call
358381
_, qErr := queryHelper(t, keeper, ctx, contractAddr, string(msg), true, tc.gasLimit)
359382
require.Empty(t, qErr)

0 commit comments

Comments
 (0)