-
Notifications
You must be signed in to change notification settings - Fork 825
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
Metering does not catch all functions that exceed limit #999
Comments
I have attempted to implement my proposed approach but I am running into issues with lifetimes and the If any of the devs or others who value accurate metering would be willing to take a look and suggest an approach I would greatly appreciate it. |
I came across this issue when doing tests with a trivial (4 points, add two i32) wasm call. Thank you for digging into this issue deeper. As to a sample test, just a lot of multiplies and adds with some variables, should produce wasm that can use eg. 100 points and never trigger a check. |
An issue that I'm having with my re-write, is that the lifetime of the I have come up with some small but sub-optimal alternative solutions: The smallest fix to the issue of not catching functions where the limit has been exceeded is to simply add Another simple and small, but not very elegant, solution to the issue of execution being able to exceed limits by an arbitrary amount, is to always inject checks after every X points without a check. This allows a user to define a maximum amount that the execution limit may be exceeded. The downside is that this reduces the issue, but still allows functions to be designed to exceed the limit by the hard coded maximum. Lower max values would result in more checks being injected which may degrade performance. On the other hand, the maximum could be chosen so as to avoid injection of additional checks for most code. In other words, there is some average number of points between the operations that cause checks for most code, and a recommended maximum could be 1.5 standard deviations above that, for example. |
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue wasmerio#999.
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue wasmerio#999.
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually exceeding the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue wasmerio#999.
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue wasmerio#999.
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue wasmerio#999.
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix wasmerio#999
Previously each Event(Operation) was passed through all MiddlewareChains one at a time. Additionally, the wasmparser library prevented cloning the Operators which prevented more than one Operator from being observed at a time. This commit loads all Operators of a function into a Vec and passes the entire Vec through the middleware chains. This design does not affect the existing MiddlewareChains but allows chains to look back at previously viewed Operators within the same function. This commit uses a forked wasmparser that adds the Clone trait to wasmparser::Operator to allow this. Looking back at previously seen Operators is necessary for allowing the metering MiddlewareChain to properly inject metering code so as to patch the flaws described in Issue #999.
Inject metering checks ahead of executing the next code block, instead of after. This ensures that execution will always return with an error as soon as it is determined that the execution limit will be exceeded, without ever actually allowing execution to exceed the limit. If execution returns with ExecutionLimitExceededError, get_points_used will return some number of points greater than the limit, since the INTERNAL_FIELD_USED is incremented and saved prior to the limit check. Fix #999
Hello @AdamSLevy, Is this issue still relevant? The problem seems solved to me as far as I can understand and try to replicate. |
Closing since the issue seems to be solved. |
Describe the bug
A function may exceed a limit set by the
wasmer_middleware_common::metering::Metering
by an arbitrary amount so long as it avoids the following instructions.Of course the points used may be checked after the fact, but this is not obvious to users of the library.
Moreover, the point of metering in many cases is to prevent execution beyond a certain point. The current metering implementation only checks if the limit was exceeded after a code segment has completed, but not even at the end of a function.
There are two issues that may be addressed separately but are related:
end
without a limit check occurring. Causing implementations to not necessarily notice that a limit was exceeded.The first issue can probably simply be resolved by adding any operators that may end a function like
Operator::End
andOperator::Return
to the list of operators that cause a limit check.The second issue is more complex because it requires that the cost of a code segment be statically known so that a check can be inserted ahead of the code segment, but since we are streaming the code the current implementation doesn't know that cost at that time.
I think this can be addressed by allowing the
feed_event
function to cacheEvents
in theMetering
struct until a complete code segment is parsed and the cost is known. Then the cost can be added and the limit checked before pushing the cached operations and starting to parse the next code segment. WhenEvent::InternalEvent::FunctionEnd
is received, the last code segment can be flushed and pushed onto theEventSink
.Steps to reproduce
I believe this behavior is actually known to the devs because the
middleware-core-tests
tests actually account for it.In this test, the limit is set at 100, and the points used is 109. In this case the function did hit an operator that caused a limit check. Nine extra operations isn't so bad, but a function could make many more without a limit check occurring
wasmer/lib/middleware-common-tests/src/lib.rs
Lines 124 to 150 in d5f25aa
A better example is this test case. This is expected to consume 74 points, so if we set the limit at 73 we should expect it to exceed the limit check and return an error during execution, but you will see that the test still passes.
wasmer/lib/middleware-common-tests/src/lib.rs
Lines 101 to 121 in d5f25aa
I am working on a better example that goes over the limit by more than one point.
Expected behavior
A function should return an
ExecutionLimitExceededError
if the limit is exceeded even by one point. Ideally a function should not proceed beyond its execution limit.Actual behavior
Some functions may return with the limit exceeded but not return an error. Even functions that return an
ExecutionLimitExceededError
will have likely performed execution beyond their limit.Additional context
I am interested in rewriting the
feed_event
function to implement my suggested approach above. Please let me know about any issues that you see with that approach.The text was updated successfully, but these errors were encountered: