Commit 5221af89 authored by smartcontracts's avatar smartcontracts Committed by Adrian Sutton

fix: have MIPS revert on add/sub overflow/underflow (#230)

Updates MIPSInstructions so that it correctly reverts on calls to
add, addi, and sub that overflow/underflow. Additionally includes
tests that demonstrates that the unchecked versions of the same
opcodes allow for overflow/underflow.
parent 5bd46ac5
...@@ -186,11 +186,29 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -186,11 +186,29 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return rs return rs
// The rest includes transformed R-type arith imm instructions // The rest includes transformed R-type arith imm instructions
case 0x20: // add case 0x20: // add
return rs + rt intRs := int32(rs)
intRt := int32(rt)
intRes := intRs + intRt
if intRs > 0 && intRt > 0 && intRes <= 0 {
panic("MIPS: add overflow")
}
if intRs < 0 && intRt < 0 && intRes >= 0 {
panic("MIPS: add underflow")
}
return uint32(intRes)
case 0x21: // addu case 0x21: // addu
return rs + rt return rs + rt
case 0x22: // sub case 0x22: // sub
return rs - rt intRs := int32(rs)
intRt := int32(rt)
intRes := intRs - intRt
if intRs > 0 && intRt < 0 && intRes < 0 {
panic("MIPS: sub overflow")
}
if intRs < 0 && intRt > 0 && intRes > 0 {
panic("MIPS: sub underflow")
}
return uint32(intRes)
case 0x23: // subu case 0x23: // subu
return rs - rt return rs - rt
case 0x24: // and case 0x24: // and
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"time" "time"
...@@ -48,6 +49,7 @@ func TestEVM(t *testing.T) { ...@@ -48,6 +49,7 @@ func TestEVM(t *testing.T) {
oracle := testutil.SelectOracleFixture(t, f.Name()) oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
evm := testutil.NewMIPSEVM(contracts, addrs) evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer) evm.SetTracer(tracer)
...@@ -66,6 +68,17 @@ func TestEVM(t *testing.T) { ...@@ -66,6 +68,17 @@ func TestEVM(t *testing.T) {
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil) goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
// Catch panics and check if they are expected
defer func() {
if r := recover(); r != nil {
if expectPanic {
// Success
} else {
t.Errorf("unexpected panic: %v", r)
}
}
}()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
curStep := goState.GetState().GetStep() curStep := goState.GetState().GetStep()
if goState.GetState().GetPC() == testutil.EndAddr { if goState.GetState().GetPC() == testutil.EndAddr {
......
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x7fff # A = 0x7fffffff (maximum positive 32-bit integer)
ori $t0, 0xffff
ori $t1, $0, 1 # B = 0x1
add $t2, $t0, $t1 # C = A + B (this should trigger an overflow)
# Unreachable ....
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x8000 # A = 0x80000000 (minimum negative 32-bit integer)
ori $t0, 0x0000
lui $t1, 0x8000 # B = 0x80000000 (minimum negative 32-bit integer)
ori $t1, 0x0000
add $t2, $t0, $t1 # C = A + B (this should trigger an underflow)
# Unreachable ....
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x7fff # A = 0x7fffffff (maximum positive 32-bit integer)
ori $t0, 0xffff
addi $t1, $t0, 1 # B = A + 1 (this should trigger an overflow)
# Unreachable...
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x8000 # A = 0x80000000 (minimum negative 32-bit integer)
ori $t0, 0x0000
addi $t1, $t0, -1 # B = A - 1 (this should trigger an underflow)
# Unreachable...
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0xffff # A = 0xffffffff (maximum unsigned 32-bit integer)
ori $t0, 0xffff
ori $t1, $0, 1 # B = 1
addu $t2, $t0, $t1 # C = A + B (simulate overflow)
sltu $v0, $t2, $t0 # D = 1 if overflow (C < A)
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x7fff # A = 0x7fffffff (maximum positive 32-bit integer)
ori $t0, 0xffff
lui $t1, 0xffff # B = 0xffffffff (-1)
ori $t1, 0xffff
sub $t2, $t0, $t1 # C = A - B = 0x7fffffff - (-1) = 0x80000000 (overflow)
# Unreachable...
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0x8000 # A = 0x80000000 (minimum negative 32-bit integer)
ori $t0, 0x0000
ori $t1, $0, 1 # B = 1
sub $t2, $t0, $t1 # C = A - B = 0x80000000 - 1 (underflow)
# Unreachable...
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
ori $t0, $0, 1 # A = 1
ori $t1, $0, 2 # B = 2
subu $t2, $t0, $t1 # C = A - B = 0xFFFFFFFF
sltu $v0, $t0, $t1 # D = 1 if underflow occurred (A < B)
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -32,6 +33,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa ...@@ -32,6 +33,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
oracle := SelectOracleFixture(t, f.Name()) oracle := SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
// TODO: currently tests are compiled as flat binary objects // TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py // We can use more standard tooling to compile them to ELF files and get remove maketests.py
...@@ -51,6 +53,17 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa ...@@ -51,6 +53,17 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger()) us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger())
// Catch panics and check if they are expected
defer func() {
if r := recover(); r != nil {
if expectPanic {
// Success
} else {
t.Errorf("unexpected panic: %v", r)
}
}
}()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if us.GetState().GetPC() == EndAddr { if us.GetState().GetPC() == EndAddr {
break break
......
...@@ -140,11 +140,11 @@ ...@@ -140,11 +140,11 @@
"sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f" "sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0xfc2381da4ad202f1ff7b97776be0e9a510ef3c36c0f557cbada1a0943f376e62", "initCodeHash": "0x67e75c05afbb472210294f922631a3f5762b05216ba2351c493aa846e5b45dfc",
"sourceCodeHash": "0x5f5d13be282305b2947079cbf8f01e750e5c88a2b7a7a7267e79cba3ee4616fa" "sourceCodeHash": "0x5f5d13be282305b2947079cbf8f01e750e5c88a2b7a7a7267e79cba3ee4616fa"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0xd75b7541ca736dff03b1fa7116a9835f97bc82507287bf90f3dd739299f5aa03", "initCodeHash": "0x926af65d34d68a15333d127f5354ff2dcb6307fa51b799f597fd3c648b5c901e",
"sourceCodeHash": "0x115bd6a4c4d77ed210dfd468675b409fdae9f79b932063c138f0765ba9063462" "sourceCodeHash": "0x115bd6a4c4d77ed210dfd468675b409fdae9f79b932063c138f0765ba9063462"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
......
...@@ -291,7 +291,12 @@ library MIPSInstructions { ...@@ -291,7 +291,12 @@ library MIPSInstructions {
// The rest includes transformed R-type arith imm instructions // The rest includes transformed R-type arith imm instructions
// add // add
else if (_fun == 0x20) { else if (_fun == 0x20) {
return (_rs + _rt); int32 rs = int32(_rs);
int32 rt = int32(_rt);
int32 res = rs + rt;
require(!(rs > 0 && rt > 0 && res <= 0), "MIPS: add overflow");
require(!(rs < 0 && rt < 0 && res >= 0), "MIPS: add underflow");
return uint32(res);
} }
// addu // addu
else if (_fun == 0x21) { else if (_fun == 0x21) {
...@@ -299,7 +304,12 @@ library MIPSInstructions { ...@@ -299,7 +304,12 @@ library MIPSInstructions {
} }
// sub // sub
else if (_fun == 0x22) { else if (_fun == 0x22) {
return (_rs - _rt); int32 rs = int32(_rs);
int32 rt = int32(_rt);
int32 res = rs - rt;
require(!(rs > 0 && rt < 0 && res <= 0), "MIPS: sub overflow");
require(!(rs < 0 && rt > 0 && res >= 0), "MIPS: sub underflow");
return uint32(res);
} }
// subu // subu
else if (_fun == 0x23) { else if (_fun == 0x23) {
......
...@@ -27,7 +27,7 @@ contract DeploymentSummary is DeploymentSummaryCode { ...@@ -27,7 +27,7 @@ contract DeploymentSummary is DeploymentSummaryCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60; address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x1620d4F04388901B3a12303df1F235Bb0C92B53D; address internal constant mipsAddress = 0xecD4f82974C78282709e1C318ef108AA5c4631D3;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567; address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131; address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -27,7 +27,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode { ...@@ -27,7 +27,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60; address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x1620d4F04388901B3a12303df1F235Bb0C92B53D; address internal constant mipsAddress = 0xecD4f82974C78282709e1C318ef108AA5c4631D3;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567; address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131; address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment