-
Notifications
You must be signed in to change notification settings - Fork 220
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
fix(tariscript): protect compare and check height from underflows #5872
fix(tariscript): protect compare and check height from underflows #5872
Conversation
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 looks good, just the error type
// This differs from compare_height due to a stack number being used, which can be lower than 0 | ||
let item = match block_height.checked_sub(height) { | ||
Some(num) => StackItem::Number(num), | ||
None => return Err(ScriptError::CompareFailed), |
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.
None => return Err(ScriptError::CompareFailed), | |
None => return Err(ScriptError::StackUnderflow), |
I would rather return the actual error here:
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.
At one point I had the same thought but the StackUnderflow
isn't a general Underflow
error. I believe StackUnderflow
is a specific error to the Input Stack underflowing during script execution. Which isn't the error here.
We could create an alternative error though. Change CompareFailed
to Underflow
or CompareFailed(message)
with a specific message.
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.
CompareFailed(message)
is probably the best way forward. Compare failed lets you believe the comparison failed, not that its an underflow.
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 suggest MathematicalUnderflow
^^ to differentaiate from StackUnderflow
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.
Updated to CompareFailed(message)
// height does not use a stack number and it's minimum can't be lower than 0. | ||
let item = match target_height.checked_sub(block_height) { | ||
Some(num) => StackItem::Number(num), | ||
None => return Err(ScriptError::CompareFailed), |
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.
same here
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.
Hi @brianp, please see the comments below, especially the one in fn handle_compare_height
.
let item = StackItem::Number(block_height - target_height); | ||
// Here it is possible to underflow because the stack can take lower numbers where check | ||
// height does not use a stack number and it's minimum can't be lower than 0. | ||
let item = match target_height.checked_sub(block_height) { |
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 believe the order is the wrong way around here when compared to the original? Why did it change?
let item = match target_height.checked_sub(block_height) { | |
let item = match block_height.checked_sub(target_height) { |
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 previous behaviour is incorrect based on the documentation:
/// Pops the top of the stack as
height
, then pushes the value of (height
- the current height) to the stack.
/// In other words, this opcode replaces the top of the stack with the difference betweenheight
and the
/// current height. Fails withInvalidInput
if there is not a valid integer value on top of the stack. Fails
/// withStackUnderflow
if the stack is empty.
So the question becomes which one is correct, the behaviour, or the documentation. I decided to trust the docs, and correct the behaviour. Do you have a particular reasoning for wanting to do the inverse?
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.
Spoke with @SWvheerden who agrees with you on this @hansieodendaal. So I've reverted the operands to the original positions and updated the op codes docs to match this behaviour.
@@ -49,6 +49,8 @@ pub enum ScriptError { | |||
VerifyFailed, | |||
#[error("as_hash requires a Digest function that returns at least 32 bytes")] | |||
InvalidDigest, | |||
#[error("A compare opcode failed, aborting the script immediately")] | |||
CompareFailed, |
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.
CompareFailed, | |
MathematicalUnderflow, |
// This differs from compare_height due to a stack number being used, which can be lower than 0 | ||
let item = match block_height.checked_sub(height) { | ||
Some(num) => StackItem::Number(num), | ||
None => return Err(ScriptError::CompareFailed), |
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 suggest MathematicalUnderflow
^^ to differentaiate from StackUnderflow
@@ -918,7 +930,7 @@ mod test { | |||
let ctx = context_with_height(u64::try_from(block_height).unwrap()); | |||
assert_eq!( | |||
script.execute_with_context(&inputs, &ctx).unwrap(), | |||
Number(block_height - 5) | |||
Number(5 - block_height) |
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 would rather retain the original behaviour
7bd4076
to
d4d7175
Compare
The opscode docs define this function as the input height from the stack subtract the current block height. This was being done opposite which would result in reversed arithmetic. Also had underflow protection from the subtraction.
This check ins't needed due to the logic build up before its use but I've added it so we don't raise and false positives or convern in the future, and it protects against future changes. I also made not about the reasons it differs between its counterpart. And some test cases.
Update unseen existing test. The failure represents the exact change, and update will make it match new changes.
d4d7175
to
e1beaab
Compare
Description
This PR corrects the operand position of the
handle_compare_height
function. This was identified as incorrect based on the op code description.Additionally, protect it from underflows, and write test cases against all possible returns and errors.
I added underflow protection in the
handle_check_height
function as well but after some thought this function wouldn't currently be possible to underflow. The values passed into the function would never be lower than 0, and then converted to i64 which would handle subtraction from 0 fine. In opposition, thehandle_compare_height
uses a value from the stack that could be i64::MIN and then have i64::MAX subtracted from it. Which was the originally identified problem. This is all to say the functions aren't quite equal so it felt worth a comment for future readers.Motivation and Context
Closes #5813
How Has This Been Tested?
Added new tests.
What process can a PR reviewer use to test or verify this change?
Double check the docs to validate the operand switch is a valid change.
See the new underflow protection.
Breaking Changes