-
Notifications
You must be signed in to change notification settings - Fork 33
SC: Sync call entry point validation for eosvm, eosvm-jit, and eosvmoc
#1455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cee8fe4
84d7222
b8e74eb
321481f
91aad6f
0d047f1
bd95d68
fa7a3c6
13e5f5e
fc69fd0
e63966f
14cb1e3
80aaab9
96076ac
729bb53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #include <eosio/chain/code_object.hpp> | ||
| #include <eosio/chain/wasm_interface.hpp> | ||
|
|
||
| namespace eosio { namespace chain { | ||
|
|
||
| void code_object::reflector_init() | ||
| { | ||
| sync_call_supported = wasm_interface::is_sync_call_supported(code.data(), code.size()); | ||
| } | ||
|
|
||
| }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,7 +65,20 @@ struct setcode_options { | |
| static constexpr bool allow_zero_blocktype = true; | ||
| }; | ||
|
|
||
| void validate(const bytes& code, const whitelisted_intrinsics_type& intrinsics) { | ||
| static bool module_has_valid_sync_call(module& mod) { | ||
| bool supported = false; | ||
| const uint32_t i = mod.get_exported_function("sync_call"); | ||
| if (i < std::numeric_limits<uint32_t>::max()) { | ||
| const vm::func_type& function_type = mod.get_function_type(i); | ||
| if (function_type == vm::host_function{{vm::i64, vm::i64, vm::i32}, {}}) { | ||
| supported = true; | ||
| } | ||
| } | ||
| return supported; | ||
| } | ||
|
|
||
| validate_result validate(const bytes& code, const whitelisted_intrinsics_type& intrinsics) { | ||
| bool sync_call_supported = false; | ||
| wasm_code_ptr code_ptr((uint8_t*)code.data(), code.size()); | ||
| try { | ||
| eos_vm_null_backend_t<setcode_options> bkend(code_ptr, code.size(), nullptr); | ||
|
|
@@ -80,13 +93,20 @@ void validate(const bytes& code, const whitelisted_intrinsics_type& intrinsics) | |
| ("module", std::string((char*)imports[i].module_str.raw(), imports[i].module_str.size())) | ||
| ("fn", std::string((char*)imports[i].field_str.raw(), imports[i].field_str.size()))); | ||
| } | ||
|
|
||
| sync_call_supported = module_has_valid_sync_call(bkend.get_module()); | ||
| } catch(vm::exception& e) { | ||
| EOS_THROW(wasm_serialization_error, e.detail()); | ||
| } | ||
|
|
||
| return validate_result { | ||
| .sync_call_supported = sync_call_supported | ||
| }; | ||
| } | ||
|
|
||
| void validate( const controller& control, const bytes& code, const wasm_config& cfg, const whitelisted_intrinsics_type& intrinsics ) { | ||
| validate_result validate( const controller& control, const bytes& code, const wasm_config& cfg, const whitelisted_intrinsics_type& intrinsics ) { | ||
| EOS_ASSERT(code.size() <= cfg.max_module_bytes, wasm_serialization_error, "Code too large"); | ||
| bool sync_call_supported = false; | ||
| wasm_code_ptr code_ptr((uint8_t*)code.data(), code.size()); | ||
| try { | ||
| eos_vm_null_backend_t<wasm_config> bkend(code_ptr, code.size(), nullptr, cfg); | ||
|
|
@@ -106,9 +126,33 @@ void validate( const controller& control, const bytes& code, const wasm_config& | |
| EOS_ASSERT(apply_idx < std::numeric_limits<uint32_t>::max(), wasm_serialization_error, "apply not exported"); | ||
| const vm::func_type& apply_type = bkend.get_module().get_function_type(apply_idx); | ||
| EOS_ASSERT((apply_type == vm::host_function{{vm::i64, vm::i64, vm::i64}, {}}), wasm_serialization_error, "apply has wrong type"); | ||
|
|
||
| sync_call_supported = module_has_valid_sync_call(bkend.get_module()); | ||
| } catch(vm::exception& e) { | ||
| EOS_THROW(wasm_serialization_error, e.detail()); | ||
| } | ||
|
|
||
| return validate_result { | ||
| .sync_call_supported = sync_call_supported | ||
| }; | ||
| } | ||
|
|
||
| bool is_sync_call_supported(const char* code_bytes, size_t code_size) { | ||
| wasm_code_ptr code_ptr((uint8_t*)code_bytes, code_size); | ||
| bool supported = false; | ||
| try { | ||
| // NOTE: if we ever relax WASM validation via some protocol feature in the future, | ||
| // we cannot simply create a backend using the default `setcode_options`. | ||
| // The caller of `is_sync_call_supported()` (`code_object::reflector_init()`) | ||
| // needs to pass in `controller` so we know what protocol features are enabled | ||
| // to decide what kind of backend to create. Or `code_object::sync_call_supported` | ||
| // needs to be populated explicitly when snapshot is read. | ||
| eos_vm_null_backend_t<setcode_options> bkend(code_ptr, code_size, nullptr); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. afaict this is good now, but it's something to be mindful of in the future -- if we ever relax WASM validation via some protocol feature in the future, this could cause problems.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What problems might be? Do you mean previously non-supported becomes supported?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When activating So creating a However let's say we made a protocol feature that relaxed validation for the "read only host functions" we've considered; let's just call that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the detailed explanation. If that happens, we will have to save |
||
| supported = module_has_valid_sync_call(bkend.get_module()); | ||
| } catch(vm::exception& e) { | ||
| EOS_THROW(wasm_serialization_error, e.detail()); | ||
| } | ||
| return supported; | ||
| } | ||
|
|
||
| // Be permissive on apply. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you already look it up here. Seems like it would be cleaner to pass
code_objectintoexecute. Thencode_hash,vm_type, andvm_versiondo not have to be passed in.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apply_contextuses the sameexecute()and it does not dipcode_object. Even if we pass incode_objecthere, we still need to split it and pass incode_hash,vm_typeandvm_versionbefore callingwasm_interface_private::execute()to reuse the same code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
apply_contextmight as well look it up and pass it in. In either case it is needed insidewasm_interface_private::execute().There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only looks
code_objectwhen the code is not cached. We will need twoexecute()s with different signatures to avoid unnecessary lookup forapply_context. Can we do this refactor in a separate PR?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point about
code_objectnot always needing to be looked up. Maybe we should cache thesync_call_supportedinwasm_cache_entryandcode_descriptor. That would mean it would have to validated inexecute().There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have
call_offsetfor oc we can check. Seems a bit fragile to rely on the JIT check, so probably should check it. So add it to thewasm_cache_entry?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really though, since it isn't checking for signature now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In current VM JIT implementation, it will crash if
sync_call()is not exported. In OC, we have an assert ifcode.call_offsetis 0.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to do a separate PR for the sync call case to avoid second look up of code_object in
wasm_interface_private::get_or_build_instantiated_module().There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1461
Will maintain the current simplicity