Skip to content

Commit

Permalink
core/vm: Limit JUMPDEST analysis only to code section of EOF
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Sep 29, 2022
1 parent 0932483 commit b5013e1
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 15 deletions.
13 changes: 7 additions & 6 deletions core/vm/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,21 @@ func (bits *bitvec) codeSegment(pos uint64) bool {
}

// codeBitmap collects data locations in code.
func codeBitmap(code []byte) bitvec {
func codeBitmap(c *Contract) bitvec {
// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will push zeroes onto the
// bitvector outside the bounds of the actual code.
bits := make(bitvec, len(code)/8+1+4)
return codeBitmapInternal(code, bits)
bits := make(bitvec, c.CodeSize()/8+1+4)
return codeBitmapInternal(c, bits)
}

// codeBitmapInternal is the internal implementation of codeBitmap.
// It exists for the purpose of being able to run benchmark tests
// without dynamic allocations affecting the results.
func codeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
op := OpCode(code[pc])
func codeBitmapInternal(c *Contract, bits bitvec) bitvec {
codeBegin := c.CodeBeginOffset()
for pc := uint64(0); pc < c.CodeSize(); {
op := OpCode(c.Code[codeBegin+pc])
pc++
if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false).
continue
Expand Down
33 changes: 28 additions & 5 deletions core/vm/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"math/bits"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

Expand Down Expand Up @@ -52,7 +53,15 @@ func TestJumpDestAnalysis(t *testing.T) {
{[]byte{byte(PUSH32)}, 0b0000_0001, 4},
}
for i, test := range tests {
ret := codeBitmap(test.code)
var (
contract Contract
addr common.Address
hash common.Hash
header EOF1Header
)
contract.SetCallCode(&addr, hash, test.code, &header)

ret := codeBitmap(&contract)
if ret[test.which] != test.exp {
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
}
Expand All @@ -64,10 +73,17 @@ const analysisCodeSize = 1200 * 1024
func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
// 1.4 ms
code := make([]byte, analysisCodeSize)
bench.SetBytes(analysisCodeSize)
var (
contract Contract
addr common.Address
hash common.Hash
header EOF1Header
)
contract.SetCallCode(&addr, hash, code, &header)
bench.ResetTimer()
bench.SetBytes(analysisCodeSize)
for i := 0; i < bench.N; i++ {
codeBitmap(code)
codeBitmap(&contract)
}
bench.StopTimer()
}
Expand All @@ -83,20 +99,27 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
}

func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
var op OpCode
var (
contract Contract
addr common.Address
hash common.Hash
header EOF1Header
op OpCode
)
bencher := func(b *testing.B) {
code := make([]byte, analysisCodeSize)
b.SetBytes(analysisCodeSize)
for i := range code {
code[i] = byte(op)
}
bits := make(bitvec, len(code)/8+1+4)
contract.SetCallCode(&addr, hash, code, &header)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range bits {
bits[j] = 0
}
codeBitmapInternal(code, bits)
codeBitmapInternal(&contract, bits)
}
}
for op = PUSH1; op <= PUSH32; op++ {
Expand Down
15 changes: 11 additions & 4 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
udest, overflow := dest.Uint64WithOverflow()
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
if overflow || udest >= uint64(len(c.Code)) {
if overflow || udest >= c.CodeSize() {
return false
}
// Only JUMPDESTs allowed for destinations
if OpCode(c.Code[udest]) != JUMPDEST {
if OpCode(c.Code[c.CodeBeginOffset()+udest]) != JUMPDEST {
return false
}
return c.isCode(udest)
Expand All @@ -114,7 +114,7 @@ func (c *Contract) isCode(udest uint64) bool {
if !exist {
// Do the analysis and save in parent context
// We do not need to store it in c.analysis
analysis = codeBitmap(c.Code)
analysis = codeBitmap(c)
c.jumpdests[c.CodeHash] = analysis
}
// Also stash it in current contract for faster access
Expand All @@ -126,7 +126,7 @@ func (c *Contract) isCode(udest uint64) bool {
// we don't have to recalculate it for every JUMP instruction in the execution
// However, we don't save it within the parent context
if c.analysis == nil {
c.analysis = codeBitmap(c.Code)
c.analysis = codeBitmap(c)
}
return c.analysis.codeSegment(udest)
}
Expand Down Expand Up @@ -201,6 +201,13 @@ func (c *Contract) CodeEndOffset() uint64 {
return c.header.CodeEndOffset()
}

func (c *Contract) CodeSize() uint64 {
if c.IsLegacy() {
return uint64(len(c.Code))
}
return uint64(c.header.codeSize)
}

// SetCallCode sets the code of the contract and address of the backing data
// object
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, header *EOF1Header) {
Expand Down

0 comments on commit b5013e1

Please sign in to comment.