-
Notifications
You must be signed in to change notification settings - Fork 15
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
int64_t code_size = (int64_t)act.code.size(); | ||
|
||
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.
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
@@ -174,6 +175,7 @@ void apply_eosio_setcode(apply_context& context) { | |||
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
const auto& pso = control.db().get<protocol_state_object>(); | ||
|
||
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 |
@@ -101,6 +101,11 @@ int64_t host_context::execute_sync_call(name call_receiver, uint64_t flags, std: | |||
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_offset
for 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
int64_t code_size = (int64_t)act.code.size(); | ||
|
||
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_call
entry point exists. If it does, its signature is checked. A flagsync_call_supported
is added tocode_object
. Before a sync call is executed,sync_call_supported
is used to determine whether the call be proceeded.sync_call_supported
is not written into snapshots. It is populated from the code when reading from snapshots.Resolves #1219 and #1392