eth/tracers: pad memory slice on OOB case (#25213)

* eth/tracers: pad memory slice on oob case

* eth/tracers/js: fix testfailure due to err msg capitalization

Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
Sina Mahmoodi 2022-09-26 09:56:45 +02:00 committed by GitHub
parent 367e60549a
commit 4dc212d4f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 13 deletions

View File

@ -33,6 +33,10 @@ import (
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
) )
const (
memoryPadLimit = 1024 * 1024
)
var assetTracers = make(map[string]string) var assetTracers = make(map[string]string)
// init retrieves the JavaScript transaction tracers included in go-ethereum. // init retrieves the JavaScript transaction tracers included in go-ethereum.
@ -562,10 +566,15 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
if end < begin || begin < 0 { if end < begin || begin < 0 {
return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
} }
if mo.memory.Len() < int(end) { mlen := mo.memory.Len()
return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), begin, end-begin) if end-int64(mlen) > memoryPadLimit {
return nil, fmt.Errorf("tracer reached limit for padding memory slice: end %d, memorySize %d", end, mlen)
} }
return mo.memory.GetCopy(begin, end-begin), nil slice := make([]byte, end-begin)
end = min(end, int64(mo.memory.Len()))
ptr := mo.memory.GetPtr(begin, end-begin)
copy(slice[:], ptr[:])
return slice, nil
} }
func (mo *memoryObj) GetUint(addr int64) goja.Value { func (mo *memoryObj) GetUint(addr int64) goja.Value {
@ -945,3 +954,10 @@ func (l *steplog) setupObject() *goja.Object {
o.Set("contract", l.contract.setupObject()) o.Set("contract", l.contract.setupObject())
return o return o
} }
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}

View File

@ -60,7 +60,7 @@ func testCtx() *vmContext {
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
} }
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
var ( var (
env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
gasLimit uint64 = 31000 gasLimit uint64 = 31000
@ -69,6 +69,9 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
contract = vm.NewContract(account{}, account{}, value, startGas) contract = vm.NewContract(account{}, account{}, value, startGas)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
if contractCode != nil {
contract.Code = contractCode
}
tracer.CaptureTxStart(gasLimit) tracer.CaptureTxStart(gasLimit)
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
@ -83,22 +86,23 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
} }
func TestTracer(t *testing.T) { func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) { execTracer := func(code string, contract []byte) ([]byte, string) {
t.Helper() t.Helper()
tracer, err := newJsTracer(code, nil, nil) tracer, err := newJsTracer(code, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ret, err := runTrace(tracer, testCtx(), params.TestChainConfig) ret, err := runTrace(tracer, testCtx(), params.TestChainConfig, contract)
if err != nil { if err != nil {
return nil, err.Error() // Stringify to allow comparison without nil checks return nil, err.Error() // Stringify to allow comparison without nil checks
} }
return ret, "" return ret, ""
} }
for i, tt := range []struct { for i, tt := range []struct {
code string code string
want string want string
fail string fail string
contract []byte
}{ }{
{ // tests that we don't panic on bad arguments to memory access { // tests that we don't panic on bad arguments to memory access
code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
@ -139,9 +143,18 @@ func TestTracer(t *testing.T) {
}, { }, {
code: "{res: null, step: function(log) { var address = Array.prototype.slice.call(log.contract.getAddress()); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", code: "{res: null, step: function(log) { var address = Array.prototype.slice.call(log.contract.getAddress()); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}",
want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`,
}, {
code: "{res: [], step: function(log) { var op = log.op.toString(); if (op === 'MSTORE8' || op === 'STOP') { this.res.push(log.memory.slice(0, 2)) } }, fault: function() {}, result: function() { return this.res }}",
want: `[{"0":0,"1":0},{"0":255,"1":0}]`,
contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)},
}, {
code: "{res: [], step: function(log) { if (log.op.toString() === 'STOP') { this.res.push(log.memory.slice(5, 1025 * 1024)) } }, fault: function() {}, result: function() { return this.res }}",
want: "",
fail: "tracer reached limit for padding memory slice: end 1049600, memorySize 32 at step (<eval>:1:83(23)) in server-side tracer function 'step'",
contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)},
}, },
} { } {
if have, err := execTracer(tt.code); tt.want != string(have) || tt.fail != err { if have, err := execTracer(tt.code, tt.contract); tt.want != string(have) || tt.fail != err {
t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code) t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
} }
} }
@ -157,7 +170,7 @@ func TestHalt(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
tracer.Stop(timeout) tracer.Stop(timeout)
}() }()
if _, err = runTrace(tracer, testCtx(), params.TestChainConfig); !strings.Contains(err.Error(), "stahp") { if _, err = runTrace(tracer, testCtx(), params.TestChainConfig, nil); !strings.Contains(err.Error(), "stahp") {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
} }
} }
@ -227,7 +240,7 @@ func TestIsPrecompile(t *testing.T) {
} }
blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)} blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)}
res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -237,7 +250,7 @@ func TestIsPrecompile(t *testing.T) {
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil) tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)} blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }