From f0b92f413617f5a20ef0a5d939ab213b3f2b3785 Mon Sep 17 00:00:00 2001 From: Assaf Morami Date: Mon, 26 Jun 2023 14:54:40 +0300 Subject: [PATCH] Implement update admin inside the enclave - Fix contract key proof generation in migrate - Fix contract key usage in handle start_engine() - Rename some vars/funcs for readability - Fix admin proof check on migrate - Fix contract key proof check on handle - refactor verify_params() to have less input params --- cosmwasm/enclaves/execute/Enclave.edl | 11 ++ cosmwasm/enclaves/ffi-types/cbindgen.toml | 1 + cosmwasm/enclaves/ffi-types/src/lib.rs | 2 +- cosmwasm/enclaves/ffi-types/src/types.rs | 11 ++ .../block-verifier/src/wasm_messages.rs | 3 + .../src/contract_operations.rs | 156 +++++++++++++----- .../src/contract_validation.rs | 71 ++++---- .../contract-engine/src/external/ecalls.rs | 60 ++++++- .../contract-engine/src/external/results.rs | 18 +- .../contract_address_validation.rs | 4 +- .../src/input_validation/msg_validation.rs | 67 ++++++-- .../send_funds_validations.rs | 4 +- .../src/input_validation/sender_validation.rs | 2 + .../shared/contract-engine/src/wasm3/mod.rs | 12 +- .../enclaves/shared/cosmos-types/src/types.rs | 100 ++++++++++- .../shared/cosmwasm-types/generic/src/lib.rs | 39 +++-- .../shared/cosmwasm-types/v0.10/src/types.rs | 23 +-- cosmwasm/packages/sgx-vm/src/calls.rs | 18 +- cosmwasm/packages/sgx-vm/src/instance.rs | 11 ++ cosmwasm/packages/sgx-vm/src/lib.rs | 4 +- cosmwasm/packages/sgx-vm/src/wasmi/imports.rs | 17 +- cosmwasm/packages/sgx-vm/src/wasmi/results.rs | 22 ++- cosmwasm/packages/sgx-vm/src/wasmi/wrapper.rs | 59 ++++++- go-cosmwasm/api/bindings.h | 12 ++ go-cosmwasm/api/lib.go | 49 ++++++ go-cosmwasm/api/lib_mock.go | 16 ++ go-cosmwasm/lib.go | 32 ++++ go-cosmwasm/src/lib.rs | 70 +++++++- x/compute/internal/keeper/keeper.go | 53 +++++- 29 files changed, 809 insertions(+), 138 deletions(-) diff --git a/cosmwasm/enclaves/execute/Enclave.edl b/cosmwasm/enclaves/execute/Enclave.edl index bd11407ed..d290faa79 100644 --- a/cosmwasm/enclaves/execute/Enclave.edl +++ b/cosmwasm/enclaves/execute/Enclave.edl @@ -124,6 +124,17 @@ enclave { uintptr_t admin_proof_len ); + public UpdateAdminResult ecall_update_admin( + [in, count=env_len] const uint8_t* env, + uintptr_t env_len, + [in, count=sig_info_len] const uint8_t* sig_info, + uintptr_t sig_info_len, + [in, count=admin_len] const uint8_t* admin, + uintptr_t admin_len, + [in, count=admin_proof_len] const uint8_t* admin_proof, + uintptr_t admin_proof_len + ); + public HealthCheckResult ecall_health_check(); public uint32_t ecall_run_tests(); diff --git a/cosmwasm/enclaves/ffi-types/cbindgen.toml b/cosmwasm/enclaves/ffi-types/cbindgen.toml index 88ad73974..7c40bacc3 100644 --- a/cosmwasm/enclaves/ffi-types/cbindgen.toml +++ b/cosmwasm/enclaves/ffi-types/cbindgen.toml @@ -37,6 +37,7 @@ include = [ "InitResult", "HandleResult", "MigrateResult", + "UpdateAdminResult", "QueryResult", "OcallReturn", "HealthCheckResult", diff --git a/cosmwasm/enclaves/ffi-types/src/lib.rs b/cosmwasm/enclaves/ffi-types/src/lib.rs index 4990ef72a..6e004ad6b 100644 --- a/cosmwasm/enclaves/ffi-types/src/lib.rs +++ b/cosmwasm/enclaves/ffi-types/src/lib.rs @@ -6,7 +6,7 @@ mod types; pub use types::{ Ctx, EnclaveBuffer, EnclaveError, HandleResult, HealthCheckResult, InitResult, MigrateResult, NodeAuthResult, OcallReturn, QueryResult, RuntimeConfiguration, UntrustedVmError, - UserSpaceBuffer, + UpdateAdminResult, UserSpaceBuffer, }; // On input, the encrypted seed is expected to contain 3 values: diff --git a/cosmwasm/enclaves/ffi-types/src/types.rs b/cosmwasm/enclaves/ffi-types/src/types.rs index 11afbacc4..affde69c4 100644 --- a/cosmwasm/enclaves/ffi-types/src/types.rs +++ b/cosmwasm/enclaves/ffi-types/src/types.rs @@ -338,6 +338,17 @@ pub enum MigrateResult { }, } +#[repr(C)] +pub enum UpdateAdminResult { + Success { + admin_proof: [u8; 32], + }, + Failure { + /// The error that happened in the enclave + err: EnclaveError, + }, +} + /// This struct is returned from ecall_query. /// cbindgen:prefix-with-name #[repr(C)] diff --git a/cosmwasm/enclaves/shared/block-verifier/src/wasm_messages.rs b/cosmwasm/enclaves/shared/block-verifier/src/wasm_messages.rs index f43b2eaa2..83207dca0 100644 --- a/cosmwasm/enclaves/shared/block-verifier/src/wasm_messages.rs +++ b/cosmwasm/enclaves/shared/block-verifier/src/wasm_messages.rs @@ -9,6 +9,9 @@ pub fn message_is_wasm(msg: &protobuf::well_known_types::Any) -> bool { msg.type_url.as_str(), "/secret.compute.v1beta1.MsgExecuteContract" | "/secret.compute.v1beta1.MsgInstantiateContract" + | "/secret.compute.v1beta1.MsgMigrateContract" + | "/secret.compute.v1beta1.MsgUpdateAdmin" + | "/secret.compute.v1beta1.MsgClearAdmin" ) } diff --git a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs index 405c6505b..f11fc1b06 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs @@ -19,7 +19,9 @@ use crate::cosmwasm_config::ContractOperation; use crate::contract_validation::verify_block_info; use crate::contract_validation::{ReplyParams, ValidatedMessage}; -use crate::external::results::{HandleSuccess, InitSuccess, MigrateSuccess, QuerySuccess}; +use crate::external::results::{ + HandleSuccess, InitSuccess, MigrateSuccess, QuerySuccess, UpdateAdminSuccess, +}; use crate::message::{is_ibc_msg, parse_message}; use crate::types::ParsedMessage; @@ -130,7 +132,10 @@ pub fn init( let canonical_sender_address = to_canonical(sender)?; let canonical_admin_address = CanonicalAddr::from_vec(admin.to_vec()); - let contract_key = generate_contract_key( + // contract_key is a unique key for each contract + // it's used in state encryption to prevent the same + // encryption keys from being used for different contracts + let og_contract_key = generate_contract_key( &canonical_sender_address, &block_height, &contract_hash, @@ -148,8 +153,6 @@ pub fn init( &canonical_sender_address, contract_address, &secret_msg, - #[cfg(feature = "light-client-validation")] - msg, true, true, VerifyParamsType::Init, @@ -176,7 +179,7 @@ pub fn init( context, gas_limit, &contract_code, - &contract_key, + &og_contract_key, ContractOperation::Init, query_depth, secret_msg.nonce, @@ -191,7 +194,12 @@ pub fn init( versioned_env.set_contract_hash(&contract_hash); #[cfg(feature = "random")] - set_random_in_env(block_height, &contract_key, &mut engine, &mut versioned_env); + set_random_in_env( + block_height, + &og_contract_key, + &mut engine, + &mut versioned_env, + ); update_msg_counter(block_height); //let start = Instant::now(); @@ -228,11 +236,11 @@ pub fn init( // todo: can move the key to somewhere in the output message if we want - let admin_proof = generate_admin_proof(&canonical_admin_address.0 .0, &contract_key); + let admin_proof = generate_admin_proof(&canonical_admin_address.0 .0, &og_contract_key); Ok(InitSuccess { output, - contract_key, + contract_key: og_contract_key, admin_proof, }) } @@ -309,18 +317,11 @@ pub fn migrate( let canonical_sender_address = to_canonical(sender)?; let canonical_admin_address = CanonicalAddr::from_vec(admin.to_vec()); - let contract_key = generate_contract_key( - &canonical_sender_address, - &block_height, - &contract_hash, - &canonical_contract_address, - )?; + let og_contract_key = base_env.get_original_contract_key()?; - let og_contract_key = base_env.get_contract_key()?; + let sender_admin_proof = generate_admin_proof(&canonical_sender_address.0 .0, &og_contract_key); - let sneder_admin_proof = generate_admin_proof(&canonical_sender_address.0 .0, &og_contract_key); - - if sneder_admin_proof != admin_proof { + if admin_proof != sender_admin_proof { error!("Failed to validate sender as current admin for migrate"); return Err(EnclaveError::ValidationFailure); } @@ -337,8 +338,6 @@ pub fn migrate( &canonical_sender_address, contract_address, &secret_msg, - #[cfg(feature = "light-client-validation")] - msg, true, true, VerifyParamsType::Migrate, @@ -365,7 +364,7 @@ pub fn migrate( context, gas_limit, &contract_code, - &contract_key, + &og_contract_key, ContractOperation::Migrate, query_depth, secret_msg.nonce, @@ -379,8 +378,20 @@ pub fn migrate( versioned_env.set_contract_hash(&contract_hash); + let new_contract_key = generate_contract_key( + &canonical_sender_address, + &block_height, + &contract_hash, + &canonical_contract_address, + )?; + #[cfg(feature = "random")] - set_random_in_env(block_height, &contract_key, &mut engine, &mut versioned_env); + set_random_in_env( + block_height, + &new_contract_key, + &mut engine, + &mut versioned_env, + ); update_msg_counter(block_height); let result = engine.migrate(&versioned_env, validated_msg); @@ -410,24 +421,81 @@ pub fn migrate( // todo: can move the key to somewhere in the output message if we want let contract_key_proof = generate_contract_key_proof( - &canonical_sender_address.0 .0, + &canonical_contract_address.0 .0, &contract_code.hash(), &og_contract_key, - &contract_key, + &new_contract_key, ); debug!( "Migrate success: {:?}, {:?}", - contract_key, contract_key_proof + new_contract_key, contract_key_proof ); Ok(MigrateSuccess { output, - new_contract_key: contract_key, + new_contract_key, contract_key_proof, }) } +pub fn update_admin( + env: &[u8], + sig_info: &[u8], + admin: &[u8], + admin_proof: &[u8], +) -> Result { + debug!("Starting update_admin"); + + let base_env: BaseEnv = extract_base_env(env)?; + + #[cfg(feature = "light-client-validation")] + { + verify_block_info(&base_env)?; + } + + let (sender, contract_address, _block_height, sent_funds) = base_env.get_verification_params(); + + let canonical_sender_address = to_canonical(sender)?; + let canonical_admin_address = CanonicalAddr::from_vec(admin.to_vec()); + + let og_contract_key = base_env.get_original_contract_key()?; + + let sender_admin_proof = generate_admin_proof(&canonical_sender_address.0 .0, &og_contract_key); + + if sender_admin_proof != admin_proof { + error!("Failed to validate sender as current admin for update_admin"); + return Err(EnclaveError::ValidationFailure); + } + debug!("Validated update_admin proof successfully"); + + let parsed_sig_info: SigInfo = extract_sig_info(sig_info)?; + + verify_params( + &parsed_sig_info, + sent_funds, + &canonical_sender_address, + contract_address, + &SecretMessage { + nonce: [0; 32], + user_public_key: [0; 32], + msg: vec![], + }, + true, + true, + VerifyParamsType::UpdateAdmin, + Some(&canonical_admin_address), + )?; + + let new_admin_proof = generate_admin_proof(&canonical_admin_address.0 .0, &og_contract_key); + + debug!("update_admin success: {:?}", new_admin_proof); + + Ok(UpdateAdminSuccess { + admin_proof: new_admin_proof, + }) +} + #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] pub fn handle( context: Ctx, @@ -462,16 +530,32 @@ pub fn handle( let canonical_contract_address = to_canonical(contract_address)?; - let mut contract_key = base_env.get_contract_key()?; - + // contract_key is unique for each contract + // it's used in state encryption to prevent the same + // encryption keys from being used for different contracts + let mut contract_key = base_env.get_current_contract_key()?; validate_contract_key(&contract_key, &canonical_contract_address, &contract_code)?; if base_env.was_migrated() { - println!("Contract was migrated, setting keys to original one"); - let og_key = base_env.get_original_contract_key().unwrap(); // was_migrated checks that this won't fail - contract_key = og_key.get_key(); + println!("Contract was migrated, validating proof"); + + // was_migrated checks that these won't fail + let og_contract_key = base_env.get_original_contract_key()?; + let sent_contract_key_proof = base_env.get_contract_key_proof()?; + + let contract_key_proof = generate_contract_key_proof( + &canonical_contract_address.0 .0, + &contract_code.hash(), + &og_contract_key, + &contract_key, // this is already validated + ); + + if sent_contract_key_proof != contract_key_proof { + error!("Failed to validate contract key proof for a migrated contract"); + return Err(EnclaveError::ValidationFailure); + } - // validate proof + contract_key = og_contract_key; // used in engine for state encryption } let parsed_sig_info: SigInfo = extract_sig_info(sig_info)?; @@ -509,8 +593,6 @@ pub fn handle( &canonical_sender_address, contract_address, &secret_msg, - #[cfg(feature = "light-client-validation")] - msg, should_verify_sig_info, should_verify_input, VerifyParamsType::HandleType(parsed_handle_type), @@ -668,7 +750,7 @@ pub fn query( let canonical_contract_address = to_canonical(contract_address)?; - let contract_key = base_env.get_contract_key()?; + let contract_key = base_env.get_current_contract_key()?; validate_contract_key(&contract_key, &canonical_contract_address, &contract_code)?; @@ -719,7 +801,7 @@ fn start_engine( context: Ctx, gas_limit: u64, contract_code: &ContractCode, - contract_key: &ContractKey, + og_contract_key: &ContractKey, operation: ContractOperation, query_depth: u32, nonce: IoNonce, @@ -731,7 +813,7 @@ fn start_engine( gas_limit, WasmCosts::default(), contract_code, - *contract_key, + *og_contract_key, operation, nonce, user_public_key, diff --git a/cosmwasm/enclaves/shared/contract-engine/src/contract_validation.rs b/cosmwasm/enclaves/shared/contract-engine/src/contract_validation.rs index bad7e925f..449354b83 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/contract_validation.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/contract_validation.rs @@ -68,9 +68,9 @@ pub fn verify_block_info(base_env: &BaseEnv) -> Result<(), EnclaveError> { } #[cfg(feature = "light-client-validation")] -pub fn check_msg_matches_state(msg: &[u8]) -> bool { - let mut verified_msgs = VERIFIED_MESSAGES.lock().unwrap(); - let remaining_msgs = verified_msgs.remaining(); +pub fn check_msg_matches_state(msg: &SecretMessage) -> bool { + let mut verified_wasm_msgs = VERIFIED_MESSAGES.lock().unwrap(); + let remaining_msgs = verified_wasm_msgs.remaining(); if remaining_msgs == 0 { error!("Failed to validate message, error 0x3555"); @@ -80,9 +80,10 @@ pub fn check_msg_matches_state(msg: &[u8]) -> bool { // Msgs might fail in the sdk before they reach the enclave. In this case we need to run through // all the messages available before we can determine that there has been a failure // this isn't an attack vector since this can happen anyway by manipulating the state between executions - while verified_msgs.remaining() > 0 { - if let Some(expected_msg) = verified_msgs.get_next() { - if is_subslice(&expected_msg, msg) { + let raw_msg = msg.to_vec(); + while verified_wasm_msgs.remaining() > 0 { + if let Some(expected_msg) = verified_wasm_msgs.get_next() { + if is_subslice(&expected_msg, &raw_msg) { return true; } } @@ -92,11 +93,14 @@ pub fn check_msg_matches_state(msg: &[u8]) -> bool { // if this message fails to verify we have to fail the rest of the TX, so we won't get any // other messages - verified_msgs.clear(); + verified_wasm_msgs.clear(); false } +/// contract_key is a unique key for each contract +/// it's used in state encryption to prevent the same +/// encryption keys from being used for different contracts pub fn generate_contract_key( sender: &CanonicalAddr, block_height: &u64, @@ -115,7 +119,7 @@ pub fn generate_contract_key( // otherwise we'd have to migrate all the contract_keys every time we rotate the seed // which is doable but requires one more ecall & just unnecessary // actually using consensus_state_ikm might be entirely unnecessary here but it's too - // painful at this point to change the protocol to remove it + // painful at this point to change the validation protocol to remove it &consensus_state_ikm.genesis, &sender_id, contract_hash, @@ -364,7 +368,6 @@ pub fn verify_params( sender: &CanonicalAddr, contract_address: &HumanAddr, msg: &SecretMessage, - #[cfg(feature = "light-client-validation")] og_msg: &[u8], should_verify_sig_info: bool, should_verify_input: bool, verify_params_type: VerifyParamsType, @@ -378,7 +381,7 @@ pub fn verify_params( } #[cfg(feature = "light-client-validation")] - if !check_msg_matches_state(og_msg) { + if !check_msg_matches_state(msg) { return Err(EnclaveError::ValidationFailure); } @@ -443,10 +446,10 @@ fn verify_input( verify_params_types: VerifyParamsType, admin: Option<&CanonicalAddr>, ) -> Result<(), EnclaveError> { - let messages = get_messages(sig_info, verify_params_types)?; + let sdk_messages = get_sdk_messages(sig_info, verify_params_types)?; - let is_verified = verify_message_params( - &messages, + let is_verified = verify_input_params( + &sdk_messages, sender, sent_funds, contract_address, @@ -529,7 +532,7 @@ fn get_signer( } } -fn get_messages( +fn get_sdk_messages( sign_info: &SigInfo, verify_params_types: VerifyParamsType, ) -> Result, EnclaveError> { @@ -644,30 +647,40 @@ fn verify_callback_sig_impl( true } -fn verify_message_params( - messages: &[DirectSdkMsg], +fn verify_input_params( + sdk_messages: &[DirectSdkMsg], sender: &CanonicalAddr, sent_funds: &[Coin], contract_address: &HumanAddr, - sent_msg: &SecretMessage, + sent_wasm_input: &SecretMessage, verify_params_types: VerifyParamsType, admin: Option<&CanonicalAddr>, ) -> bool { info!("Verifying sdk message against wasm input..."); // If msg is not found (is None) then it means message verification failed, // since it didn't find a matching signed message - let sdk_msg = verify_and_get_sdk_msg(messages, sender, sent_msg, verify_params_types, admin); - if sdk_msg.is_none() { - debug!("Message verification failed!"); - trace!( - "Message sent to contract {:?} by {:?} does not match any signed messages {:?}", - sent_msg.to_vec(), - sender, - messages - ); - return false; - } - let sdk_msg = sdk_msg.unwrap(); + let sdk_msg = verify_and_get_sdk_msg( + sdk_messages, + sender, + contract_address, + sent_wasm_input, + verify_params_types, + admin, + ); + + let sdk_msg = match sdk_msg { + Some(sdk_msg) => sdk_msg, + None => { + debug!("Message verification failed!"); + trace!( + "Message sent to contract {:?} by {:?} does not match any signed messages {:?}", + sent_wasm_input.to_vec(), + sender, + sdk_messages + ); + return false; + } + }; info!("Verifying message sender..."); if let Some(value) = verify_sender(sdk_msg, sender) { diff --git a/cosmwasm/enclaves/shared/contract-engine/src/external/ecalls.rs b/cosmwasm/enclaves/shared/contract-engine/src/external/ecalls.rs index 68c133eb8..ea348f3b0 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/external/ecalls.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/external/ecalls.rs @@ -9,7 +9,7 @@ use sgx_types::sgx_status_t; use enclave_ffi_types::{ Ctx, EnclaveBuffer, EnclaveError, HandleResult, HealthCheckResult, InitResult, MigrateResult, - QueryResult, RuntimeConfiguration, + QueryResult, RuntimeConfiguration, UpdateAdminResult, }; use enclave_utils::{oom_handler, validate_const_ptr, validate_mut_ptr}; @@ -17,6 +17,7 @@ use enclave_utils::{oom_handler, validate_const_ptr, validate_mut_ptr}; use crate::external::results::{ result_handle_success_to_handleresult, result_init_success_to_initresult, result_migrate_success_to_result, result_query_success_to_queryresult, + result_update_admin_success_to_result, }; lazy_static! { @@ -477,7 +478,7 @@ pub unsafe extern "C" fn ecall_migrate( *used_gas = gas_limit / 2; if oom_handler::get_then_clear_oom_happened() { - error!("Call ecall_handle failed because the enclave ran out of memory!"); + error!("Call ecall_migrate failed because the enclave ran out of memory!"); MigrateResult::Failure { err: EnclaveError::OutOfMemory, } @@ -490,6 +491,61 @@ pub unsafe extern "C" fn ecall_migrate( } } +/// # Safety +/// Always use protection +#[no_mangle] +pub unsafe extern "C" fn ecall_update_admin( + env: *const u8, + env_len: usize, + sig_info: *const u8, + sig_info_len: usize, + admin: *const u8, + admin_len: usize, + admin_proof: *const u8, + admin_proof_len: usize, +) -> UpdateAdminResult { + if let Err(err) = oom_handler::register_oom_handler() { + error!("Could not register OOM handler!"); + return UpdateAdminResult::Failure { err }; + } + + let failed_call = + || result_update_admin_success_to_result(Err(EnclaveError::FailedFunctionCall)); + validate_const_ptr!(env, env_len as usize, failed_call()); + validate_const_ptr!(sig_info, sig_info_len as usize, failed_call()); + validate_const_ptr!(admin, admin_len as usize, failed_call()); + validate_const_ptr!(admin_proof, admin_proof_len as usize, failed_call()); + + let env = std::slice::from_raw_parts(env, env_len); + let sig_info = std::slice::from_raw_parts(sig_info, sig_info_len); + let admin = std::slice::from_raw_parts(admin, admin_len); + let admin_proof = std::slice::from_raw_parts(admin_proof, admin_proof_len); + + let result = panic::catch_unwind(|| { + let result = crate::contract_operations::update_admin(env, sig_info, admin, admin_proof); + result_update_admin_success_to_result(result) + }); + + if let Err(err) = oom_handler::restore_safety_buffer() { + error!("Could not restore OOM safety buffer!"); + return UpdateAdminResult::Failure { err }; + } + + if let Ok(res) = result { + res + } else if oom_handler::get_then_clear_oom_happened() { + error!("Call ecall_update_admin failed because the enclave ran out of memory!"); + UpdateAdminResult::Failure { + err: EnclaveError::OutOfMemory, + } + } else { + error!("Call ecall_update_admin panicked unexpectedly!"); + UpdateAdminResult::Failure { + err: EnclaveError::Panic, + } + } +} + /// # Safety /// Always use protection #[no_mangle] diff --git a/cosmwasm/enclaves/shared/contract-engine/src/external/results.rs b/cosmwasm/enclaves/shared/contract-engine/src/external/results.rs index 172ff51ab..024c7e3b9 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/external/results.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/external/results.rs @@ -2,7 +2,7 @@ use sgx_types::sgx_status_t; use enclave_ffi_types::{ EnclaveError, HandleResult, InitResult, MigrateResult, QueryResult, UntrustedVmError, - UserSpaceBuffer, + UpdateAdminResult, UserSpaceBuffer, }; use crate::external::ocalls::ocall_allocate; @@ -80,7 +80,7 @@ pub fn result_handle_success_to_handleresult( } } -/// This struct is returned from a handle method. +/// This struct is returned from a migrate method. pub struct MigrateSuccess { /// The output of the calculation pub output: Vec, @@ -121,6 +121,20 @@ pub fn result_migrate_success_to_result( } } +/// This struct is returned from a migrate method. +pub struct UpdateAdminSuccess { + pub admin_proof: [u8; 32], +} + +pub fn result_update_admin_success_to_result( + result: Result, +) -> UpdateAdminResult { + match result { + Ok(UpdateAdminSuccess { admin_proof }) => UpdateAdminResult::Success { admin_proof }, + Err(err) => UpdateAdminResult::Failure { err }, + } +} + /// This struct is returned from a query method. pub struct QuerySuccess { /// The output of the calculation diff --git a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/contract_address_validation.rs b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/contract_address_validation.rs index 1afa5e033..d1d69837f 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/contract_address_validation.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/contract_address_validation.rs @@ -10,7 +10,9 @@ pub fn verify_contract_address(msg: &DirectSdkMsg, contract_address: &HumanAddr) // Contract address is relevant only to execute, since during sending an instantiate message the contract address is not yet known match msg { DirectSdkMsg::MsgExecuteContract { contract, .. } - | DirectSdkMsg::MsgMigrateContract { contract, .. } => { + | DirectSdkMsg::MsgMigrateContract { contract, .. } + | DirectSdkMsg::MsgUpdateAdmin { contract, .. } + | DirectSdkMsg::MsgClearAdmin { contract, .. } => { verify_msg_execute_or_migrate_contract_address(contract_address, contract) } // During sending an instantiate message the contract address is not yet known diff --git a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/msg_validation.rs b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/msg_validation.rs index 357e1a7d8..dc25a13da 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/msg_validation.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/msg_validation.rs @@ -15,15 +15,16 @@ use crate::types::SecretMessage; /// Get the cosmwasm message that contains the encrypted message pub fn verify_and_get_sdk_msg<'sd>( - messages: &'sd [DirectSdkMsg], + sdk_messages: &'sd [DirectSdkMsg], sent_sender: &CanonicalAddr, - sent_msg: &SecretMessage, + sent_contract_address: &HumanAddr, + sent_wasm_input: &SecretMessage, verify_params_types: VerifyParamsType, sent_admin: Option<&CanonicalAddr>, ) -> Option<&'sd DirectSdkMsg> { - trace!("verify_and_get_sdk_msg: {:?}", messages); + trace!("verify_and_get_sdk_msg: {:?}", sdk_messages); - messages.iter().find(|&m| match m { + sdk_messages.iter().find(|&m| match m { DirectSdkMsg::Other => false, DirectSdkMsg::MsgInstantiateContract { init_msg: msg, @@ -37,24 +38,56 @@ pub fn verify_and_get_sdk_msg<'sd>( let sent_admin = sent_admin.unwrap_or(empty_canon); let sent_admin = &HumanAddr::from_canonical(sent_admin).unwrap_or(empty_human); - sent_admin == admin && sent_sender == sender && &sent_msg.to_vec() == msg + sent_admin == admin && sent_sender == sender && &sent_wasm_input.to_vec() == msg } - DirectSdkMsg::MsgExecuteContract { msg, sender, .. } => { - sent_sender == sender && &sent_msg.to_vec() == msg + DirectSdkMsg::MsgExecuteContract { + msg, + sender, + contract, + .. + } => { + sent_sender == sender + && sent_contract_address == contract + && &sent_wasm_input.to_vec() == msg } - DirectSdkMsg::MsgMigrateContract { msg, sender, .. } => { + DirectSdkMsg::MsgMigrateContract { + msg, + sender, + contract, + .. + } => { let empty_canon = &CanonicalAddr(Binary(vec![])); let sent_admin = sent_admin.unwrap_or(empty_canon); - sent_sender == sender && sent_admin == sender && &sent_msg.to_vec() == msg + sent_sender == sender + && sent_admin == sender + && sent_contract_address == contract + && &sent_wasm_input.to_vec() == msg + } + DirectSdkMsg::MsgUpdateAdmin { + sender, + new_admin, + contract, + } => { + let empty_canon = &CanonicalAddr(Binary(vec![])); + let empty_human = HumanAddr("".to_string()); + let sent_admin = &HumanAddr::from_canonical(sent_admin.unwrap_or(empty_canon)) + .unwrap_or(empty_human); + + sent_sender == sender && sent_admin == new_admin && sent_contract_address == contract + } + DirectSdkMsg::MsgClearAdmin { sender, contract } => { + sent_sender == sender + && sent_admin == Some(&CanonicalAddr(Binary(vec![]))) + && sent_contract_address == contract } DirectSdkMsg::MsgRecvPacket { packet, .. } => match verify_params_types { VerifyParamsType::HandleType(HandleType::HANDLE_TYPE_IBC_PACKET_RECEIVE) => { - verify_ibc_packet_recv(sent_msg, packet) + verify_ibc_packet_recv(sent_wasm_input, packet) } VerifyParamsType::HandleType( HandleType::HANDLE_TYPE_IBC_WASM_HOOKS_INCOMING_TRANSFER, - ) => verify_ibc_wasm_hooks_incoming_transfer(sent_msg, packet), + ) => verify_ibc_wasm_hooks_incoming_transfer(sent_wasm_input, packet), _ => false, }, DirectSdkMsg::MsgAcknowledgement { @@ -64,20 +97,24 @@ pub fn verify_and_get_sdk_msg<'sd>( .. } => match verify_params_types { VerifyParamsType::HandleType(HandleType::HANDLE_TYPE_IBC_PACKET_ACK) => { - verify_ibc_packet_ack(sent_msg, packet, acknowledgement, signer) + verify_ibc_packet_ack(sent_wasm_input, packet, acknowledgement, signer) } VerifyParamsType::HandleType( HandleType::HANDLE_TYPE_IBC_WASM_HOOKS_OUTGOING_TRANSFER_ACK, - ) => verify_ibc_wasm_hooks_outgoing_transfer_ack(sent_msg, packet, acknowledgement), + ) => verify_ibc_wasm_hooks_outgoing_transfer_ack( + sent_wasm_input, + packet, + acknowledgement, + ), _ => false, }, DirectSdkMsg::MsgTimeout { packet, signer, .. } => match verify_params_types { VerifyParamsType::HandleType(HandleType::HANDLE_TYPE_IBC_PACKET_TIMEOUT) => { - verify_ibc_packet_timeout(sent_msg, packet, signer) + verify_ibc_packet_timeout(sent_wasm_input, packet, signer) } VerifyParamsType::HandleType( HandleType::HANDLE_TYPE_IBC_WASM_HOOKS_OUTGOING_TRANSFER_TIMEOUT, - ) => verify_ibc_wasm_hooks_outgoing_transfer_timeout(sent_msg, packet), + ) => verify_ibc_wasm_hooks_outgoing_transfer_timeout(sent_wasm_input, packet), _ => false, }, }) diff --git a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/send_funds_validations.rs b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/send_funds_validations.rs index faa0d6647..4d0041e4e 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/send_funds_validations.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/send_funds_validations.rs @@ -42,7 +42,9 @@ pub fn verify_sent_funds(msg: &DirectSdkMsg, sent_funds_msg: &[Coin]) -> bool { } DirectSdkMsg::MsgAcknowledgement { .. } | DirectSdkMsg::MsgTimeout { .. } - | DirectSdkMsg::MsgMigrateContract { .. } => sent_funds_msg.is_empty(), + | DirectSdkMsg::MsgMigrateContract { .. } + | DirectSdkMsg::MsgUpdateAdmin { .. } + | DirectSdkMsg::MsgClearAdmin { .. } => sent_funds_msg.is_empty(), } } diff --git a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/sender_validation.rs b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/sender_validation.rs index 8d2f981ab..c64ead42e 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/input_validation/sender_validation.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/input_validation/sender_validation.rs @@ -13,6 +13,8 @@ pub fn verify_sender(sdk_msg: &DirectSdkMsg, sent_sender: &CanonicalAddr) -> Opt DirectSdkMsg::MsgExecuteContract { .. } | DirectSdkMsg::MsgInstantiateContract { .. } | DirectSdkMsg::MsgMigrateContract { .. } + | DirectSdkMsg::MsgUpdateAdmin { .. } + | DirectSdkMsg::MsgClearAdmin { .. } | DirectSdkMsg::Other => { if sdk_msg.sender() != Some(sent_sender) { trace!( diff --git a/cosmwasm/enclaves/shared/contract-engine/src/wasm3/mod.rs b/cosmwasm/enclaves/shared/contract-engine/src/wasm3/mod.rs index 2483b97fe..142fa8052 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/wasm3/mod.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/wasm3/mod.rs @@ -95,7 +95,7 @@ pub struct Context { gas_costs: WasmCosts, query_depth: u32, operation: ContractOperation, - contract_key: ContractKey, + og_contract_key: ContractKey, user_nonce: IoNonce, user_public_key: Ed25519PublicKey, kv_cache: KvCache, @@ -214,7 +214,7 @@ impl Engine { gas_limit: u64, gas_costs: WasmCosts, contract_code: &ContractCode, - contract_key: ContractKey, + og_contract_key: ContractKey, operation: ContractOperation, user_nonce: IoNonce, user_public_key: Ed25519PublicKey, @@ -230,7 +230,7 @@ impl Engine { gas_used_externally: 0, gas_costs, operation, - contract_key, + og_contract_key, user_nonce, user_public_key, kv_cache, @@ -633,7 +633,7 @@ impl Engine { &k, &v, &self.context.context, - &self.context.contract_key, + &self.context.og_contract_key, &get_encryption_salt(self.context.timestamp), ) .unwrap(); @@ -907,7 +907,7 @@ fn host_read_db( let (value, used_gas) = read_from_encrypted_state( &state_key_name, &context.context, - &context.contract_key, + &context.og_contract_key, match context.operation { ContractOperation::Init => true, ContractOperation::Handle => true, @@ -956,7 +956,7 @@ fn host_remove_db( context.kv_cache.remove(&state_key_name); let used_gas = - remove_from_encrypted_state(&state_key_name, &context.context, &context.contract_key)?; + remove_from_encrypted_state(&state_key_name, &context.context, &context.og_contract_key)?; context.use_gas_externally(used_gas); Ok(()) diff --git a/cosmwasm/enclaves/shared/cosmos-types/src/types.rs b/cosmwasm/enclaves/shared/cosmos-types/src/types.rs index 35abda35f..acd901d0e 100644 --- a/cosmwasm/enclaves/shared/cosmos-types/src/types.rs +++ b/cosmwasm/enclaves/shared/cosmos-types/src/types.rs @@ -208,8 +208,9 @@ pub enum VerifyParamsType { HandleType(HandleType), Init, Migrate, + /// UpdateAdmin is used both for updating the admin and clearing the admin + /// (by passing an empty admin address) UpdateAdmin, - ClearAdmin, } // This is called `VerificationInfo` on the Go side @@ -336,6 +337,15 @@ pub enum AminoSdkMsg { code_id: String, msg: String, }, + MsgUpdateAdmin { + sender: HumanAddr, + new_admin: HumanAddr, + contract: HumanAddr, + }, + MsgClearAdmin { + sender: HumanAddr, + contract: HumanAddr, + }, // The core IBC messages don't support Amino #[serde(other, deserialize_with = "deserialize_ignore_any")] Other, @@ -375,6 +385,7 @@ impl AminoSdkMsg { ); EnclaveError::FailedToDeserialize })?; + Ok(DirectSdkMsg::MsgMigrateContract { sender, msg, @@ -401,6 +412,7 @@ impl AminoSdkMsg { EnclaveError::FailedToDeserialize })?; let msg = msg.0; + Ok(DirectSdkMsg::MsgExecuteContract { sender, contract, @@ -437,6 +449,7 @@ impl AminoSdkMsg { ); EnclaveError::FailedToDeserialize })?; + Ok(DirectSdkMsg::MsgInstantiateContract { sender, code_id, @@ -447,6 +460,30 @@ impl AminoSdkMsg { admin, }) } + AminoSdkMsg::MsgUpdateAdmin { + sender, + new_admin, + contract, + } => { + let sender = CanonicalAddr::from_human(&sender).map_err(|err| { + warn!("failed to turn human addr to canonical addr when parsing DirectSdkMsg: {:?}", err); + EnclaveError::FailedToDeserialize + })?; + + Ok(DirectSdkMsg::MsgUpdateAdmin { + sender, + new_admin, + contract, + }) + } + AminoSdkMsg::MsgClearAdmin { sender, contract } => { + let sender = CanonicalAddr::from_human(&sender).map_err(|err| { + warn!("failed to turn human addr to canonical addr when parsing DirectSdkMsg: {:?}", err); + EnclaveError::FailedToDeserialize + })?; + + Ok(DirectSdkMsg::MsgClearAdmin { sender, contract }) + } Self::Other => Ok(DirectSdkMsg::Other), } } @@ -629,6 +666,15 @@ pub enum DirectSdkMsg { msg: Vec, code_id: u64, }, + MsgUpdateAdmin { + sender: CanonicalAddr, + new_admin: HumanAddr, + contract: HumanAddr, + }, + MsgClearAdmin { + sender: CanonicalAddr, + contract: HumanAddr, + }, // IBC: // MsgChannelOpenInit {}, // TODO // MsgChannelOpenTry {}, // TODO @@ -675,8 +721,7 @@ impl DirectSdkMsg { Self::try_parse_execute(bytes) } VerifyParamsType::Migrate => Self::try_parse_migrate(bytes), - VerifyParamsType::UpdateAdmin => Ok(DirectSdkMsg::Other), - VerifyParamsType::ClearAdmin => Ok(DirectSdkMsg::Other), + VerifyParamsType::UpdateAdmin => Self::try_parse_update_admin(bytes), VerifyParamsType::HandleType(HandleType::HANDLE_TYPE_REPLY) => Ok(DirectSdkMsg::Other), VerifyParamsType::HandleType(HandleType::HANDLE_TYPE_IBC_CHANNEL_OPEN) => { Ok(DirectSdkMsg::Other) @@ -832,6 +877,51 @@ impl DirectSdkMsg { }) } + fn try_parse_update_admin(bytes: &[u8]) -> Result { + use proto::cosmwasm::msg::{MsgClearAdmin, MsgUpdateAdmin}; + + // We first try to parse as MsgUpdateAdmin, if that fails we try to parse as MsgClearAdmin + // The order is important as MsgClearAdmin is a subset of MsgUpdateAdmin and can succeed parsing MsgUpdateAdmin bytes as MsgClearAdmin + + let raw_msg = MsgUpdateAdmin::parse_from_bytes(bytes); + + if let Ok(raw_msg) = raw_msg { + trace!( + "try_parse_update_admin sender: len={} val={:?}", + raw_msg.sender.len(), + raw_msg.sender + ); + + let sender = CanonicalAddr::from_human(&HumanAddr(raw_msg.sender)) + .map_err(|_| EnclaveError::FailedToDeserialize)?; + + let new_admin = HumanAddr(raw_msg.new_admin); + + Ok(DirectSdkMsg::MsgUpdateAdmin { + sender, + new_admin, + contract: HumanAddr(raw_msg.contract), + }) + } else { + let raw_update_msg = MsgClearAdmin::parse_from_bytes(bytes) + .map_err(|_| EnclaveError::FailedToDeserialize)?; + + trace!( + "try_parse_migrate sender: len={} val={:?}", + raw_update_msg.sender.len(), + raw_update_msg.sender + ); + + let sender = CanonicalAddr::from_human(&HumanAddr(raw_update_msg.sender)) + .map_err(|_| EnclaveError::FailedToDeserialize)?; + + Ok(DirectSdkMsg::MsgClearAdmin { + sender, + contract: HumanAddr(raw_update_msg.contract), + }) + } + } + fn try_parse_instantiate(bytes: &[u8]) -> Result { use proto::cosmwasm::msg::MsgInstantiateContract; @@ -926,7 +1016,9 @@ impl DirectSdkMsg { match self { DirectSdkMsg::MsgExecuteContract { sender, .. } | DirectSdkMsg::MsgInstantiateContract { sender, .. } - | DirectSdkMsg::MsgMigrateContract { sender, .. } => Some(sender), + | DirectSdkMsg::MsgMigrateContract { sender, .. } + | DirectSdkMsg::MsgUpdateAdmin { sender, .. } + | DirectSdkMsg::MsgClearAdmin { sender, .. } => Some(sender), DirectSdkMsg::MsgRecvPacket { .. } => None, DirectSdkMsg::MsgAcknowledgement { .. } => None, DirectSdkMsg::MsgTimeout { .. } => None, diff --git a/cosmwasm/enclaves/shared/cosmwasm-types/generic/src/lib.rs b/cosmwasm/enclaves/shared/cosmwasm-types/generic/src/lib.rs index c926677b3..250a99359 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-types/generic/src/lib.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-types/generic/src/lib.rs @@ -5,13 +5,14 @@ use serde::{Deserialize, Serialize}; use cw_types_v010::encoding::Binary; use cw_types_v010::types as v010types; -use cw_types_v010::types::{ContractKeyWithProof, Env as V010Env, HumanAddr}; +use cw_types_v010::types::{Env as V010Env, HumanAddr}; use cw_types_v1::types::Env as V1Env; use cw_types_v1::types::MessageInfo as V1MessageInfo; use cw_types_v1::types::{self as v1types, Addr}; use enclave_ffi_types::EnclaveError; pub const CONTRACT_KEY_LENGTH: usize = 64; +pub const CONTRACT_KEY_PROOF_LENGTH: usize = 32; /// CosmwasmApiVersion is used to decide how to handle contract inputs and outputs #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] @@ -39,15 +40,9 @@ pub type BaseCanoncalAddr = v010types::CanonicalAddr; pub struct BaseEnv(pub V010Env); impl BaseEnv { - pub fn get_contract_key(&self) -> Result<[u8; CONTRACT_KEY_LENGTH], EnclaveError> { - let contract_key = if let Some(b64_key) = &self.0.contract_key { - base64::decode(&b64_key.key).map_err(|err| { - warn!( - "got an error while trying to decode contract key {:?}: {}", - b64_key, err - ); - EnclaveError::FailedContractAuthentication - })? + pub fn get_current_contract_key(&self) -> Result<[u8; CONTRACT_KEY_LENGTH], EnclaveError> { + let contract_key = if let Some(contract_key) = &self.0.contract_key { + &contract_key.key.0 } else { warn!("Contract execute with empty contract key"); return Err(EnclaveError::FailedContractAuthentication); @@ -59,7 +54,7 @@ impl BaseEnv { } let mut key_as_bytes = [0u8; CONTRACT_KEY_LENGTH]; - key_as_bytes.copy_from_slice(&contract_key); + key_as_bytes.copy_from_slice(contract_key); Ok(key_as_bytes) } @@ -72,11 +67,27 @@ impl BaseEnv { } } - pub fn get_original_contract_key(&self) -> Option { + pub fn get_original_contract_key(&self) -> Result<[u8; CONTRACT_KEY_LENGTH], EnclaveError> { + if let Some(key) = &self.0.contract_key { + if self.was_migrated() { + Ok(key.original.clone().unwrap().get_key()) + } else { + self.get_current_contract_key() + } + } else { + Err(EnclaveError::FailedContractAuthentication) + } + } + + pub fn get_contract_key_proof(&self) -> Result<[u8; CONTRACT_KEY_PROOF_LENGTH], EnclaveError> { if let Some(key) = &self.0.contract_key { - key.original.clone() + if self.was_migrated() { + Ok(key.original.clone().unwrap().get_proof()) + } else { + Err(EnclaveError::FailedContractAuthentication) + } } else { - None + Err(EnclaveError::FailedContractAuthentication) } } diff --git a/cosmwasm/enclaves/shared/cosmwasm-types/v0.10/src/types.rs b/cosmwasm/enclaves/shared/cosmwasm-types/v0.10/src/types.rs index 19566b3f4..bfe7bccc2 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-types/v0.10/src/types.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-types/v0.10/src/types.rs @@ -20,6 +20,9 @@ use super::encoding::Binary; use crate::consts::BECH32_PREFIX_ACC_ADDR; +pub const CONTRACT_KEY_LENGTH: usize = 64; +pub const CONTRACT_KEY_PROOF_LENGTH: usize = 32; + #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)] pub struct HumanAddr(pub String); @@ -102,29 +105,27 @@ impl fmt::Display for CanonicalAddr { #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)] pub struct ContractKey { - pub key: String, + pub key: Binary, pub original: Option, } #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)] pub struct ContractKeyWithProof { - pub key: String, - pub proof: String, + pub key: Binary, + pub proof: Binary, } impl ContractKeyWithProof { - pub fn get_key(&self) -> [u8; 64] { - let mut output = [0u8; 64]; - let decoded = base64::decode(&self.key).unwrap(); - output.copy_from_slice(&decoded); + pub fn get_key(&self) -> [u8; CONTRACT_KEY_LENGTH] { + let mut output = [0u8; CONTRACT_KEY_LENGTH]; + output.copy_from_slice(&self.key.0); output } - pub fn decode_proof(&self) -> [u8; 32] { - let mut output = [0u8; 32]; - let decoded = base64::decode(&self.proof).unwrap(); - output.copy_from_slice(&decoded); + pub fn get_proof(&self) -> [u8; CONTRACT_KEY_PROOF_LENGTH] { + let mut output = [0u8; CONTRACT_KEY_PROOF_LENGTH]; + output.copy_from_slice(&self.proof.0); output } diff --git a/cosmwasm/packages/sgx-vm/src/calls.rs b/cosmwasm/packages/sgx-vm/src/calls.rs index 60d0c69cb..6181b8d52 100644 --- a/cosmwasm/packages/sgx-vm/src/calls.rs +++ b/cosmwasm/packages/sgx-vm/src/calls.rs @@ -72,7 +72,7 @@ pub fn call_query( } */ -/// Calls Wasm export "init" and returns raw data from the contract. +/// Calls Wasm export "migrate" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. pub fn call_migrate_raw( instance: &mut Instance, @@ -89,6 +89,22 @@ pub fn call_migrate_raw( + instance: &mut Instance, + env: &[u8], + sig_info: &[u8], + admin: &[u8], + admin_proof: &[u8], +) -> VmResult> { + instance.set_storage_readonly(false); + /* + call_raw(instance, "init", &[env, msg], MAX_LENGTH_INIT) + */ + instance.call_update_admin(env, sig_info, admin, admin_proof) +} + /// Calls Wasm export "init" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. pub fn call_init_raw( diff --git a/cosmwasm/packages/sgx-vm/src/instance.rs b/cosmwasm/packages/sgx-vm/src/instance.rs index e05e82776..b70521e86 100644 --- a/cosmwasm/packages/sgx-vm/src/instance.rs +++ b/cosmwasm/packages/sgx-vm/src/instance.rs @@ -338,6 +338,17 @@ where Ok(result.into_output()) } + pub fn call_update_admin( + &mut self, + env: &[u8], + sig_info: &[u8], + admin: &[u8], + admin_proof: &[u8], + ) -> VmResult> { + let result = self.inner.update_admin(env, sig_info, admin, admin_proof)?; + Ok(result.into_output()) + } + pub fn call_init( &mut self, env: &[u8], diff --git a/cosmwasm/packages/sgx-vm/src/lib.rs b/cosmwasm/packages/sgx-vm/src/lib.rs index a43e0371f..2f6dd50d2 100644 --- a/cosmwasm/packages/sgx-vm/src/lib.rs +++ b/cosmwasm/packages/sgx-vm/src/lib.rs @@ -30,7 +30,9 @@ mod random; pub mod enclave_tests; pub use crate::cache::CosmCache; -pub use crate::calls::{call_handle_raw, call_init_raw, call_migrate_raw, call_query_raw}; +pub use crate::calls::{ + call_handle_raw, call_init_raw, call_migrate_raw, call_query_raw, call_update_admin_raw, +}; pub use crate::checksum::Checksum; pub use crate::errors::{ CommunicationError, CommunicationResult, RegionValidationError, RegionValidationResult, diff --git a/cosmwasm/packages/sgx-vm/src/wasmi/imports.rs b/cosmwasm/packages/sgx-vm/src/wasmi/imports.rs index a516b3854..5c8ccf002 100644 --- a/cosmwasm/packages/sgx-vm/src/wasmi/imports.rs +++ b/cosmwasm/packages/sgx-vm/src/wasmi/imports.rs @@ -4,7 +4,9 @@ use log::*; use sgx_types::{sgx_enclave_id_t, sgx_status_t, SgxResult}; -use enclave_ffi_types::{Ctx, EnclaveBuffer, HandleResult, InitResult, MigrateResult, QueryResult}; +use enclave_ffi_types::{ + Ctx, EnclaveBuffer, HandleResult, InitResult, MigrateResult, QueryResult, UpdateAdminResult, +}; use crate::enclave::ENCLAVE_DOORBELL; @@ -37,6 +39,19 @@ extern "C" { admin_proof_len: usize, ) -> sgx_status_t; + pub fn ecall_update_admin( + eid: sgx_enclave_id_t, + retval: *mut UpdateAdminResult, + env: *const u8, + env_len: usize, + sig_info: *const u8, + sig_info_len: usize, + admin: *const u8, + admin_len: usize, + admin_proof: *const u8, + admin_proof_len: usize, + ) -> sgx_status_t; + /// Trigger the init method in a wasm contract pub fn ecall_init( eid: sgx_enclave_id_t, diff --git a/cosmwasm/packages/sgx-vm/src/wasmi/results.rs b/cosmwasm/packages/sgx-vm/src/wasmi/results.rs index 063ce918e..99ee6f72d 100644 --- a/cosmwasm/packages/sgx-vm/src/wasmi/results.rs +++ b/cosmwasm/packages/sgx-vm/src/wasmi/results.rs @@ -1,6 +1,6 @@ use super::exports; use crate::VmResult; -use enclave_ffi_types::{HandleResult, InitResult, MigrateResult, QueryResult}; +use enclave_ffi_types::{HandleResult, InitResult, MigrateResult, QueryResult, UpdateAdminResult}; /// This struct is returned from module initialization. pub struct InitSuccess { @@ -50,7 +50,14 @@ pub fn migrate_result_to_vm_result(other: MigrateResult) -> VmResult VmResult { + match other { + UpdateAdminResult::Success { admin_proof } => Ok(UpdateAdminSuccess { admin_proof }), + UpdateAdminResult::Failure { err } => Err(err.into()), + } +} + +/// This struct is returned from a migrate method. pub struct MigrateSuccess { /// A pointer to the output of the execution output: Vec, @@ -67,6 +74,17 @@ impl MigrateSuccess { } } +/// This struct is returned from a migrate method. +pub struct UpdateAdminSuccess { + admin_proof: [u8; 32], +} + +impl UpdateAdminSuccess { + pub fn into_output(self) -> Vec { + self.admin_proof.to_vec() + } +} + /// This struct is returned from a handle method. pub struct HandleSuccess { /// A pointer to the output of the execution diff --git a/cosmwasm/packages/sgx-vm/src/wasmi/wrapper.rs b/cosmwasm/packages/sgx-vm/src/wasmi/wrapper.rs index 7e19f982d..89856ea5e 100644 --- a/cosmwasm/packages/sgx-vm/src/wasmi/wrapper.rs +++ b/cosmwasm/packages/sgx-vm/src/wasmi/wrapper.rs @@ -10,11 +10,15 @@ use crate::enclave::ENCLAVE_DOORBELL; use crate::errors::{EnclaveError, VmResult}; use crate::{Querier, Storage, VmError}; -use enclave_ffi_types::{Ctx, HandleResult, InitResult, MigrateResult, QueryResult}; +use enclave_ffi_types::{ + Ctx, HandleResult, InitResult, MigrateResult, QueryResult, UpdateAdminResult, +}; use sgx_types::sgx_status_t; -use crate::wasmi::results::{migrate_result_to_vm_result, MigrateSuccess}; +use crate::wasmi::results::{ + migrate_result_to_vm_result, update_admin_result_to_vm_result, MigrateSuccess, +}; use log::*; use serde::Deserialize; @@ -22,7 +26,7 @@ use super::exports::FullContext; use super::imports; use super::results::{ handle_result_to_vm_result, init_result_to_vm_result, query_result_to_vm_result, HandleSuccess, - InitSuccess, QuerySuccess, + InitSuccess, QuerySuccess, UpdateAdminSuccess, }; pub struct Module @@ -102,7 +106,7 @@ where admin_proof: &[u8], ) -> VmResult { trace!( - "init() called with env: {:?} msg: {:?} gas_left: {}", + "migrate() called with env: {:?} msg: {:?} gas_left: {}", String::from_utf8_lossy(env), String::from_utf8_lossy(msg), self.gas_left() @@ -156,6 +160,53 @@ where } } + pub fn update_admin( + &mut self, + env: &[u8], + sig_info: &[u8], + admin: &[u8], + admin_proof: &[u8], + ) -> VmResult { + trace!( + "update_admin() called with env: {:?}", + String::from_utf8_lossy(env), + ); + + let mut update_admin_result = MaybeUninit::::uninit(); + + // Bind the token to a local variable to ensure its + // destructor runs in the end of the function + let enclave_access_token = ENCLAVE_DOORBELL + .get_access(1) // This can never be recursive + .ok_or_else(Self::busy_enclave_err)?; + let enclave = enclave_access_token.map_err(EnclaveError::sdk_err)?; + + let status = unsafe { + imports::ecall_update_admin( + enclave.geteid(), + update_admin_result.as_mut_ptr(), + env.as_ptr(), + env.len(), + sig_info.as_ptr(), + sig_info.len(), + admin.as_ptr(), + admin.len(), + admin_proof.as_ptr(), + admin_proof.len(), + ) + }; + + trace!("update_admin() returned"); + + match status { + sgx_status_t::SGX_SUCCESS => { + let update_admin_result = unsafe { update_admin_result.assume_init() }; + update_admin_result_to_vm_result(update_admin_result) + } + failure_status => Err(EnclaveError::sdk_err(failure_status).into()), + } + } + pub fn init( &mut self, env: &[u8], diff --git a/go-cosmwasm/api/bindings.h b/go-cosmwasm/api/bindings.h index 69e984da1..20093a827 100644 --- a/go-cosmwasm/api/bindings.h +++ b/go-cosmwasm/api/bindings.h @@ -237,3 +237,15 @@ Buffer submit_block_signatures(Buffer header, Buffer txs, Buffer random, Buffer *err); + +Buffer update_admin(cache_t *cache, + Buffer contract_id, + Buffer params, + DB db, + GoApi api, + GoQuerier querier, + uint64_t gas_limit, + Buffer *err, + Buffer sig_info, + Buffer admin, + Buffer admin_proof); diff --git a/go-cosmwasm/api/lib.go b/go-cosmwasm/api/lib.go index 01409f0d7..73577ad8e 100644 --- a/go-cosmwasm/api/lib.go +++ b/go-cosmwasm/api/lib.go @@ -203,6 +203,55 @@ func Migrate( return receiveVector(res), uint64(gasUsed), nil } +func UpdateAdmin( + cache Cache, + code_id []byte, + params []byte, + gasMeter *GasMeter, + store KVStore, + api *GoAPI, + querier *Querier, + gasLimit uint64, + sigInfo []byte, + admin []byte, + adminProof []byte, +) ([]byte, error) { + id := sendSlice(code_id) + defer freeAfterSend(id) + p := sendSlice(params) + defer freeAfterSend(p) + + // set up a new stack frame to handle iterators + counter := startContract() + defer endContract(counter) + + dbState := buildDBState(store, counter) + db := buildDB(&dbState, gasMeter) + + s := sendSlice(sigInfo) + defer freeAfterSend(s) + a := buildAPI(api) + q := buildQuerier(querier) + errmsg := C.Buffer{} + + adminBuffer := sendSlice(admin) + defer freeAfterSend(adminBuffer) + + adminProofBuffer := sendSlice(adminProof) + defer freeAfterSend(adminProofBuffer) + + //// This is done in order to ensure that goroutines don't + //// swap threads between recursive calls to the enclave. + //runtime.LockOSThread() + //defer runtime.UnlockOSThread() + + res, err := C.update_admin(cache.ptr, id, p, db, a, q, u64(gasLimit), &errmsg, s, adminBuffer, adminProofBuffer) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + return nil, errorWithMessage(err, errmsg) + } + return receiveVector(res), nil +} + func Instantiate( cache Cache, code_id []byte, diff --git a/go-cosmwasm/api/lib_mock.go b/go-cosmwasm/api/lib_mock.go index c94892f8d..59ebe0d98 100644 --- a/go-cosmwasm/api/lib_mock.go +++ b/go-cosmwasm/api/lib_mock.go @@ -112,6 +112,22 @@ func Migrate( return nil, 0, nil } +func UpdateAdmin( + cache Cache, + code_id []byte, + params []byte, + gasMeter *GasMeter, + store KVStore, + api *GoAPI, + querier *Querier, + gasLimit uint64, + sigInfo []byte, + admin []byte, + adminProof []byte, +) ([]byte, error) { + return nil, nil +} + func Instantiate( cache Cache, code_id []byte, diff --git a/go-cosmwasm/lib.go b/go-cosmwasm/lib.go index 35397e9c5..8b3082d12 100644 --- a/go-cosmwasm/lib.go +++ b/go-cosmwasm/lib.go @@ -493,3 +493,35 @@ func (w *Wasmer) Migrate( return nil, nil, nil, gasUsed, fmt.Errorf("instantiate: cannot detect response type (v0.10 or v1)") } + +// UpdateAdmin will update or clear a contract admin. +func (w *Wasmer) UpdateAdmin( + newCodeId CodeHash, + env types.Env, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + sigInfo types.VerificationInfo, + admin []byte, + adminProof []byte, + // newAdminProof, error +) ([]byte, error) { + paramBin, err := json.Marshal(env) + if err != nil { + return nil, err + } + + sigInfoBin, err := json.Marshal(sigInfo) + if err != nil { + return nil, err + } + + newAdminProof, err := api.UpdateAdmin(w.cache, newCodeId, paramBin, &gasMeter, store, &goapi, &querier, gasLimit, sigInfoBin, admin, adminProof) + if err != nil { + return nil, err + } + + return newAdminProof, nil +} diff --git a/go-cosmwasm/src/lib.rs b/go-cosmwasm/src/lib.rs index 2bc9200c3..3f0bcb9e0 100644 --- a/go-cosmwasm/src/lib.rs +++ b/go-cosmwasm/src/lib.rs @@ -21,8 +21,8 @@ use crate::error::{clear_error, handle_c_error, handle_c_error_default, set_erro use cosmwasm_sgx_vm::untrusted_init_bootstrap; use cosmwasm_sgx_vm::{ - call_handle_raw, call_init_raw, call_migrate_raw, call_query_raw, features_from_csv, Checksum, - CosmCache, Extern, + call_handle_raw, call_init_raw, call_migrate_raw, call_query_raw, call_update_admin_raw, + features_from_csv, Checksum, CosmCache, Extern, }; use cosmwasm_sgx_vm::{ create_attestation_report_u, untrusted_get_encrypted_genesis_seed, @@ -576,6 +576,72 @@ fn do_migrate( Ok(res?) } +#[no_mangle] +pub extern "C" fn update_admin( + cache: *mut cache_t, + contract_id: Buffer, + params: Buffer, + db: DB, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + err: Option<&mut Buffer>, + sig_info: Buffer, + admin: Buffer, + admin_proof: Buffer, +) -> Buffer { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || { + do_update_admin( + c, + contract_id, + params, + db, + api, + querier, + gas_limit, + sig_info, + admin, + admin_proof, + ) + })) + .unwrap_or_else(|_| Err(Error::panic())), + None => Err(Error::empty_arg(CACHE_ARG)), + }; + let data = handle_c_error(r, err); + Buffer::from_vec(data) +} + +#[allow(clippy::too_many_arguments)] +fn do_update_admin( + cache: &mut CosmCache, + code_id: Buffer, + params: Buffer, + db: DB, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + sig_info: Buffer, + admin: Buffer, + admin_proof: Buffer, +) -> Result, Error> { + let code_id: Checksum = unsafe { code_id.read() } + .ok_or_else(|| Error::empty_arg(CODE_ID_ARG))? + .try_into()?; + let params = unsafe { params.read() }.ok_or_else(|| Error::empty_arg(PARAMS_ARG))?; + let sig_info = unsafe { sig_info.read() }.ok_or_else(|| Error::empty_arg(SIG_INFO_ARG))?; + let admin = unsafe { admin.read() }.ok_or_else(|| Error::empty_arg(ADMIN_ARG))?; + let admin_proof = + unsafe { admin_proof.read() }.ok_or_else(|| Error::empty_arg(ADMIN_PROOF_ARG))?; + + let deps = to_extern(db, api, querier); + let mut instance = cache.get_instance(&code_id, deps, gas_limit)?; + // We only check this result after reporting gas usage and returning the instance into the cache. + let res = call_update_admin_raw(&mut instance, params, sig_info, admin, admin_proof); + instance.recycle(); + Ok(res?) +} + #[no_mangle] pub extern "C" fn handle( cache: *mut cache_t, diff --git a/x/compute/internal/keeper/keeper.go b/x/compute/internal/keeper/keeper.go index 1b81dedbb..c870dc8f7 100644 --- a/x/compute/internal/keeper/keeper.go +++ b/x/compute/internal/keeper/keeper.go @@ -1201,15 +1201,60 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply v1w } func (k Keeper) UpdateContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress) error { - contractInfo := k.GetContractInfo(ctx, contractAddress) - if contractInfo == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") + defer telemetry.MeasureSince(time.Now(), "compute", "keeper", "update-contract-admin") + ctx.GasMeter().ConsumeGas(types.InstanceCost, "Loading CosmWasm module: update-contract-admin") + + contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) + if err != nil { + return err } if contractInfo.Admin != caller.String() { return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "caller is not the admin") } + + signBytes, signMode, modeInfoBytes, pkBytes, signerSig, err := k.GetTxInfo(ctx, caller) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + } + + verificationInfo := types.NewVerificationInfo(signBytes, signMode, modeInfoBytes, pkBytes, signerSig, nil) + + contractKey, err := k.GetContractKey(ctx, contractAddress) + if err != nil { + return err + } + + env := types.NewEnv(ctx, caller, sdk.Coins{}, contractAddress, &contractKey, nil) + + currentAdminProof := k.GetContractInfo(ctx, contractAddress).AdminProof + currentAdmin := k.GetContractInfo(ctx, contractAddress).Admin + + currentAdminAddress, err := sdk.AccAddressFromBech32(currentAdmin) + if err != nil { + return err + } + + // prepare querier + // TODO: this is unnecessary, get rid of this + querier := QueryHandler{ + Ctx: ctx, + Plugins: k.queryPlugins, + Caller: contractAddress, + } + + // instantiate wasm contract + gas := gasForContract(ctx) + + newAdminProof, updateAdminErr := k.wasmer.UpdateAdmin(codeInfo.CodeHash, env, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas, verificationInfo, currentAdminAddress, currentAdminProof) + + if updateAdminErr != nil { + return updateAdminErr + } + contractInfo.Admin = newAdmin.String() - k.setContractInfo(ctx, contractAddress, contractInfo) + contractInfo.AdminProof = newAdminProof + k.setContractInfo(ctx, contractAddress, &contractInfo) + ctx.EventManager().EmitEvent(sdk.NewEvent( types.EventTypeUpdateContractAdmin, sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()),