From 87dd82d09de3fe8974e64233b3a48b718dfa86bd Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 09:21:26 +0100 Subject: [PATCH] Add addr_validate import --- packages/vm/src/compatibility.rs | 2 + packages/vm/src/environment.rs | 1 + packages/vm/src/imports.rs | 125 +++++++++++++++++++++++++++++++ packages/vm/src/instance.rs | 15 +++- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 634b066fdf..784105c460 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -13,6 +13,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.db_read", "env.db_write", "env.db_remove", + "env.addr_validate", "env.addr_canonicalize", "env.addr_humanize", "env.secp256k1_verify", @@ -321,6 +322,7 @@ mod tests { (import "env" "db_read" (func (param i32 i32) (result i32))) (import "env" "db_write" (func (param i32 i32) (result i32))) (import "env" "db_remove" (func (param i32) (result i32))) + (import "env" "addr_validate" (func (param i32) (result i32))) (import "env" "addr_canonicalize" (func (param i32 i32) (result i32))) (import "env" "addr_humanize" (func (param i32 i32) (result i32))) (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index fcea6517e3..fae0fa860f 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -424,6 +424,7 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), + "addr_validate" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index d2ca3b9397..e44224345b 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -74,6 +74,13 @@ pub fn native_db_remove( do_remove(env, key_ptr) } +pub fn native_addr_validate( + env: &Environment, + source_ptr: u32, +) -> VmResult { + do_addr_validate(&env, source_ptr) +} + pub fn native_addr_canonicalize( env: &Environment, source_ptr: u32, @@ -226,6 +233,37 @@ fn do_remove( Ok(()) } +fn do_addr_validate( + env: &Environment, + source_ptr: u32, +) -> VmResult { + let source_data = read_region(&env.memory(), source_ptr, MAX_LENGTH_HUMAN_ADDRESS)?; + if source_data.is_empty() { + return Ok(write_to_contract::(env, b"Input is empty")?); + } + + let source_string = match String::from_utf8(source_data) { + Ok(s) => s, + Err(_) => { + return Ok(write_to_contract::( + env, + b"Input is not valid UTF-8", + )?) + } + }; + let human: HumanAddr = source_string.into(); + + let (result, gas_info) = env.api.canonical_address(&human); + process_gas_info::(env, gas_info)?; + match result { + Ok(_canonical) => Ok(0), + Err(BackendError::UserErr { msg, .. }) => { + Ok(write_to_contract::(env, msg.as_bytes())?) + } + Err(err) => Err(VmError::from(err)), + } +} + fn do_addr_canonicalize( env: &Environment, source_ptr: u32, @@ -569,6 +607,7 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), + "addr_validate" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), @@ -896,6 +935,92 @@ mod tests { } } + #[test] + fn do_addr_validate_works() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, b"foo"); + + leave_default_data(&env); + + let res = do_addr_validate::(&env, source_ptr).unwrap(); + assert_eq!(res, 0); + } + + #[test] + fn do_addr_validate_reports_invalid_input_back_to_contract() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr1 = write_data(&env, b"fo\x80o"); // invalid UTF-8 (fo�o) + let source_ptr2 = write_data(&env, b""); // empty + let source_ptr3 = write_data(&env, b"addressexceedingaddressspace"); // too long + + leave_default_data(&env); + + let res = do_addr_validate::(&env, source_ptr1).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Input is not valid UTF-8"); + + let res = do_addr_validate::(&env, source_ptr2).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Input is empty"); + + let res = do_addr_validate::(&env, source_ptr3).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Invalid input: human address too long"); + } + + #[test] + fn do_addr_validate_fails_for_broken_backend() { + let api = MockApi::new_failing("Temporarily unavailable"); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, b"foo"); + + leave_default_data(&env); + + let result = do_addr_validate::(&env, source_ptr); + match result.unwrap_err() { + VmError::BackendErr { + source: BackendError::Unknown { msg, .. }, + .. + } => { + assert_eq!(msg.unwrap(), "Temporarily unavailable"); + } + err => panic!("Incorrect error returned: {:?}", err), + } + } + + #[test] + fn do_addr_validate_fails_for_large_inputs() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, &vec![61; 100]); + + leave_default_data(&env); + + let result = do_addr_validate::(&env, source_ptr); + match result.unwrap_err() { + VmError::CommunicationErr { + source: + CommunicationError::RegionLengthTooBig { + length, max_length, .. + }, + .. + } => { + assert_eq!(length, 100); + assert_eq!(max_length, 90); + } + err => panic!("Incorrect error returned: {:?}", err), + } + } + #[test] fn do_addr_canonicalize_works() { let api = MockApi::default(); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index b3757fa54b..731eea0124 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -9,9 +9,10 @@ use crate::environment::Environment; use crate::errors::{CommunicationError, VmError, VmResult}; use crate::features::required_features_from_wasmer_instance; use crate::imports::{ - native_addr_canonicalize, native_addr_humanize, native_db_read, native_db_remove, - native_db_write, native_debug, native_ed25519_batch_verify, native_ed25519_verify, - native_query_chain, native_secp256k1_recover_pubkey, native_secp256k1_verify, + native_addr_canonicalize, native_addr_humanize, native_addr_validate, native_db_read, + native_db_remove, native_db_write, native_debug, native_ed25519_batch_verify, + native_ed25519_verify, native_query_chain, native_secp256k1_recover_pubkey, + native_secp256k1_verify, }; #[cfg(feature = "iterator")] use crate::imports::{native_db_next, native_db_scan}; @@ -105,6 +106,14 @@ where Function::new_native_with_env(store, env.clone(), native_db_remove), ); + // Reads human address from source_ptr and checks if it is valid. + // Returns 0 on if the input is valid. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs. + // Ownership of the input pointer is not transferred to the host. + env_imports.insert( + "addr_validate", + Function::new_native_with_env(store, env.clone(), native_addr_validate), + ); + // Reads human address from source_ptr and writes canonicalized representation to destination_ptr. // A prepared and sufficiently large memory Region is expected at destination_ptr that points to pre-allocated memory. // Returns 0 on success. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs.