-
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
Conversation
libraries/chain/eosio_contract.cpp
Outdated
| if( code_size > 0 ) { | ||
| code_hash = fc::sha256::hash( act.code.data(), (uint32_t)act.code.size() ); | ||
| wasm_interface::validate(context.control, act.code); | ||
| wasm_interface::validate(context.control, act.code, sync_call_supported); |
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 was debating whether I should do the sync_call check here inside validate(), or call is_sync_call_supported() separately so not changing validate()'s signature. I opted to do it in validate() to save reconstruction of vm::backend from the code and an extra call to is_sync_call_supported().
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.
Returning a struct of bools (only sync_call_supported for now) would be better I think.
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.
Initially I thought about returning a bool, but that does not sound right as the method name is validate. A struct of bools is a better choice. Thanks.
| uint8_t vm_type = 0; | ||
| uint8_t vm_version = 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'm a little confused on this new snapshot_code_object_v1 because it's the same as the code_object previously was serialized. Also, when we're converting from different chainbase<->snapshot types usually (always?) we use snapshot_row_traits which I don't see anywhere in here.
I'm suspicious we don't need any of these snapshot changes and can instead add a code_object::reflector_init() which just populates the new sync_call_supported field. (this would mean that we'd not include sync_call_supported as part of the fc_reflect which is a little weird but not unheard of)
I'm not 100% confident on the above but I'd suggest trying it
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.
The new snapshot_code_object_v1 is for snapshot versions prior to sync call protocol feature. We have similar structs for block_state and chain_config.
Will try using code_object::reflector_init(). Thanks.
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.
ah okay yeah I saw maximum_version = 8; but I forgot we were on 9 on this branch
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.
Thanks!
code_object::reflector_init() works. We don't need to place sync_call_supported into snapshots.
spoonincode
left a comment
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.
Looks really good
libraries/chain/eosio_contract.cpp
Outdated
| if( new_code_entry ) { | ||
| db.modify(*new_code_entry, [&](code_object& o) { | ||
| ++o.code_ref_count; | ||
| o.sync_call_supported = sync_call_supported; |
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.
This line is unnecessary.
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.
libraries/chain/wasm_interface.cpp
Outdated
| if (control.is_builtin_activated(builtin_protocol_feature_t::configurable_wasm_limits)) { | ||
| const auto& gpo = control.get_global_properties(); | ||
| webassembly::eos_vm_runtime::validate( control, code, gpo.wasm_configuration, pso.whitelisted_intrinsics ); | ||
| webassembly::eos_vm_runtime::validate( control, code, gpo.wasm_configuration, pso.whitelisted_intrinsics, sync_call_supported ); |
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 know it is academic for Vaulta, but in the case where CONFIGURABLE_WASM_LIMITS2 has not be activated we fall down below and don't populate sync_call_supported. We really should populate it both places.
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 thought about it. Because sync_call depends on all previous protocol features, CONFIGURABLE_WASM_LIMITS2 should have been activated. That's why I did not add to the other validate(). But will add for completeness and for not missing some corner cases.
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.
Thanks for all your suggestions |
| return handle_call_failure(); | ||
| } | ||
|
|
||
| const code_object* const codeobject = db.find<code_object, by_code_hash>(boost::make_tuple(receiver_account->code_hash, receiver_account->vm_type, receiver_account->vm_version)); |
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_object into execute. Then code_hash, vm_type, and vm_version do 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_context uses the same execute() and it does not dip code_object. Even if we pass in code_object here, we still need to split it and pass in code_hash, vm_type and vm_version before calling wasm_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_context might as well look it up and pass it in. In either case it is needed inside wasm_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_object when the code is not cached. We will need two execute()s with different signatures to avoid unnecessary lookup for apply_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_object not always needing to be looked up. Maybe we should cache the sync_call_supported in wasm_cache_entry and code_descriptor. That would mean it would have to validated in 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.
We already have call_offset for oc we can check. Seems a bit fragile to rely on the JIT check, so probably should check it. So add it to the wasm_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.
We already have
call_offsetfor oc we can check
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 if code.call_offset is 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.
Will maintain the current simplicity
libraries/chain/eosio_contract.cpp
Outdated
| if( code_size > 0 ) { | ||
| code_hash = fc::sha256::hash( act.code.data(), (uint32_t)act.code.size() ); | ||
| wasm_interface::validate(context.control, act.code); | ||
| wasm_interface::validate(context.control, act.code, sync_call_supported); |
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.
Returning a struct of bools (only sync_call_supported for now) would be better I think.
…ntry point function
| wasm_code_ptr code_ptr((uint8_t*)code_bytes, code_size); | ||
| bool supported = false; | ||
| try { | ||
| eos_vm_null_backend_t<setcode_options> bkend(code_ptr, code_size, nullptr); |
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.
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.
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.
What problems might be? Do you mean previously non-supported becomes supported?
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.
When activating CONFIGURABLE_WASM_LIMITS2 validation becomes more strict on setcode to prevent some violations of wasm spec. (contracts that violate these rules would still work after activation but could not be setcode again)
So creating a backend with pre-CONFIGURABLE_WASM_LIMITS2 rules is okay since CONFIGURABLE_WASM_LIMITS2 is more strict.
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 READONLY_HF. This would be a problem because here we'd not allow that relaxation since we're still validating against pre-READONLY_HF (and pre-CONFIGURABLE_WASM_LIMITS2) rules. We'd need access to what protocol features are enabled to know what kind of backend to create, and we don't have access to that information readily in the reflector_init()
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.
Thanks for the detailed explanation. If that happens, we will have to save sync_call_supported in the snapshot, so that the caller of is_sync_call_supported() can pass in controller to have access to protocol feature set. I will add a comment.
This PR implements a new method to validate sync call entry point. It is applicable to
eosvm,eosvm-jit, andeosvmoc. When an account code is deployed, check whethersync_callentry point exists. If it does, its signature is checked. A flagsync_call_supportedis added tocode_object. Before a sync call is executed,sync_call_supportedis used to determine whether the call be proceeded.sync_call_supportedis not written into snapshots. It is populated from the code when reading from snapshots.Resolves #1219 and #1392