diff --git a/plugin/dapp/evm/executor/evm.go b/plugin/dapp/evm/executor/evm.go index fac4f7f90..486459d08 100644 --- a/plugin/dapp/evm/executor/evm.go +++ b/plugin/dapp/evm/executor/evm.go @@ -7,6 +7,7 @@ package executor import ( "bytes" "fmt" + "math" "math/big" "os" "reflect" @@ -209,7 +210,8 @@ func (evm *EVMExecutor) createEvmContractAddress(b common.Address, nonce uint64) // CheckTx 校验交易 func (evm *EVMExecutor) CheckTx(tx *types.Transaction, index int) error { - if evm.GetAPI().GetConfig().IsPara() { + cfg := evm.GetAPI().GetConfig() + if cfg.IsPara() { return nil } @@ -217,6 +219,20 @@ func (evm *EVMExecutor) CheckTx(tx *types.Transaction, index int) error { return fmt.Errorf("tx empty") } + // 分叉后拒绝金额超过 int64 上限的交易: + // EVMContractAction.Amount 为 uint64,下游记账体系为 int64, + // 超过 math.MaxInt64 会回绕为负数,曾导致伪 msg.value 攻击(详见 ForkEVMFixOverflow) + if cfg.IsDappFork(evm.GetMainHeight(), "evm", evmtypes.ForkEVMFixOverflow) { + var action evmtypes.EVMContractAction + if err := types.Decode(tx.Payload, &action); err != nil { + return err + } + if action.GetAmount() > math.MaxInt64 { + log.Error("CheckTx", "reject evm tx with amount overflow", "amount", action.GetAmount()) + return types.ErrAmount + } + } + return state.ProcessCheck(evm.GetMainHeight(), tx.Hash()) } diff --git a/plugin/dapp/evm/executor/evm_checktx_amount_test.go b/plugin/dapp/evm/executor/evm_checktx_amount_test.go new file mode 100644 index 000000000..9cfd021f1 --- /dev/null +++ b/plugin/dapp/evm/executor/evm_checktx_amount_test.go @@ -0,0 +1,61 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package executor + +import ( + "math" + "testing" + + ctypes "github.com/33cn/chain33/types" + evmtypes "github.com/33cn/plugin/plugin/dapp/evm/types" + "github.com/stretchr/testify/require" +) + +// makeCallTxWithAmount builds a signed evm call tx carrying the given amount. +func makeCallTxWithAmount(cfg *ctypes.Chain33Config, amount uint64) *ctypes.Transaction { + action := &evmtypes.EVMContractAction{ + Amount: amount, GasLimit: 100000, GasPrice: 1, + Code: nil, Para: []byte("test"), + ContractAddr: "0x0000000000000000000000000000000000000000", + } + tx := &ctypes.Transaction{ + ChainID: cfg.GetChainID(), + Execer: []byte(cfg.ExecName(evmtypes.ExecutorName)), + Payload: ctypes.Encode(action), + Fee: 1e6, Nonce: 0, + } + signTx(tx, roleAttacker) + return tx +} + +// After ForkEVMFixOverflow, CheckTx must reject evm txs whose uint64 Amount +// exceeds math.MaxInt64 (the value that used to wrap to -1 and bypass the +// balance check, enabling the fake msg.value attack). Before the fork the +// historical behavior is preserved (only replay check runs). +func TestCheckTxRejectsAmountOverflow(t *testing.T) { + cfg := newTestConfig(t) // ForkEVMFixOverflow = 1000 + + // post-fork: overflow amount rejected at CheckTx + exec := newTestExecutor(t, cfg, 1000) + tx := makeCallTxWithAmount(cfg, math.MaxUint64) + require.Equal(t, ctypes.ErrAmount, exec.CheckTx(tx, 0)) + + tx = makeCallTxWithAmount(cfg, math.MaxInt64+1) + require.Equal(t, ctypes.ErrAmount, exec.CheckTx(tx, 0)) + + // post-fork: boundary and normal amounts pass + tx = makeCallTxWithAmount(cfg, math.MaxInt64) + require.NoError(t, exec.CheckTx(tx, 0)) + tx = makeCallTxWithAmount(cfg, 1e8) + require.NoError(t, exec.CheckTx(tx, 0)) + tx = makeCallTxWithAmount(cfg, 0) + require.NoError(t, exec.CheckTx(tx, 0)) + + // pre-fork: historical behavior preserved (overflow amount NOT rejected here; + // the exec-layer fork gate handles it on such chains) + execPre := newTestExecutor(t, cfg, 999) + tx = makeCallTxWithAmount(cfg, math.MaxUint64) + require.NoError(t, execPre.CheckTx(tx, 0)) +}