fuzz: Fix off by one error in sparse merkle trie item insertion (#10668)

* fuzz: Fix off by one error in sparse merkle trie item insertion

* remove new line

* Move validation to the proto constructor

* fix build

* Add a unit test because @nisdas is going to ask for it

* fix up

* gaz

* Update container/trie/sparse_merkle.go

* Update container/trie/sparse_merkle_test.go

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: nisdas <nishdas93@gmail.com>
This commit is contained in:
Preston Van Loon 2022-05-13 02:02:43 -05:00 committed by GitHub
parent e771585b77
commit 7042791e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 35 deletions

View File

@ -137,6 +137,7 @@ go_test(
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/wrapper:go_default_library", "//consensus-types/wrapper:go_default_library",
"//container/trie:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",
@ -188,6 +189,7 @@ go_test(
"//beacon-chain/powchain/testing:go_default_library", "//beacon-chain/powchain/testing:go_default_library",
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/wrapper:go_default_library", "//consensus-types/wrapper:go_default_library",
"//container/trie:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",

View File

@ -31,6 +31,7 @@ import (
"github.com/prysmaticlabs/prysm/config/params" "github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/consensus-types/wrapper" "github.com/prysmaticlabs/prysm/consensus-types/wrapper"
"github.com/prysmaticlabs/prysm/container/trie"
"github.com/prysmaticlabs/prysm/encoding/bytesutil" "github.com/prysmaticlabs/prysm/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/testing/assert" "github.com/prysmaticlabs/prysm/testing/assert"
@ -86,9 +87,11 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service {
bState, _ := util.DeterministicGenesisState(t, 10) bState, _ := util.DeterministicGenesisState(t, 10)
pbState, err := v1.ProtobufBeaconState(bState.InnerStateUnsafe()) pbState, err := v1.ProtobufBeaconState(bState.InnerStateUnsafe())
require.NoError(t, err) require.NoError(t, err)
mockTrie, err := trie.NewTrie(0)
require.NoError(t, err)
err = beaconDB.SavePowchainData(ctx, &ethpb.ETH1ChainData{ err = beaconDB.SavePowchainData(ctx, &ethpb.ETH1ChainData{
BeaconState: pbState, BeaconState: pbState,
Trie: &ethpb.SparseMerkleTrie{}, Trie: mockTrie.ToProto(),
CurrentEth1Data: &ethpb.LatestETH1Data{ CurrentEth1Data: &ethpb.LatestETH1Data{
BlockHash: make([]byte, 32), BlockHash: make([]byte, 32),
}, },

View File

@ -767,9 +767,12 @@ func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *ethpb.ET
if eth1DataInDB == nil { if eth1DataInDB == nil {
return nil return nil
} }
s.depositTrie = trie.CreateTrieFromProto(eth1DataInDB.Trie)
s.chainStartData = eth1DataInDB.ChainstartData
var err error var err error
s.depositTrie, err = trie.CreateTrieFromProto(eth1DataInDB.Trie)
if err != nil {
return err
}
s.chainStartData = eth1DataInDB.ChainstartData
if !reflect.ValueOf(eth1DataInDB.BeaconState).IsZero() { if !reflect.ValueOf(eth1DataInDB.BeaconState).IsZero() {
s.preGenesisState, err = v1.InitializeFromProto(eth1DataInDB.BeaconState) s.preGenesisState, err = v1.InitializeFromProto(eth1DataInDB.BeaconState)
if err != nil { if err != nil {

View File

@ -13,6 +13,7 @@ go_library(
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//math:go_default_library", "//math:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"@com_github_pkg_errors//:go_default_library",
], ],
) )

View File

@ -4,9 +4,9 @@ package trie
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/crypto/hash" "github.com/prysmaticlabs/prysm/crypto/hash"
"github.com/prysmaticlabs/prysm/encoding/bytesutil" "github.com/prysmaticlabs/prysm/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/math" "github.com/prysmaticlabs/prysm/math"
@ -29,7 +29,7 @@ func NewTrie(depth uint64) (*SparseMerkleTrie, error) {
} }
// CreateTrieFromProto creates a Sparse Merkle Trie from its corresponding merkle trie. // CreateTrieFromProto creates a Sparse Merkle Trie from its corresponding merkle trie.
func CreateTrieFromProto(trieObj *protodb.SparseMerkleTrie) *SparseMerkleTrie { func CreateTrieFromProto(trieObj *protodb.SparseMerkleTrie) (*SparseMerkleTrie, error) {
trie := &SparseMerkleTrie{ trie := &SparseMerkleTrie{
depth: uint(trieObj.Depth), depth: uint(trieObj.Depth),
originalItems: trieObj.OriginalItems, originalItems: trieObj.OriginalItems,
@ -39,7 +39,29 @@ func CreateTrieFromProto(trieObj *protodb.SparseMerkleTrie) *SparseMerkleTrie {
branches[i] = layer.Layer branches[i] = layer.Layer
} }
trie.branches = branches trie.branches = branches
return trie
if err := trie.validate(); err != nil {
return nil, errors.Wrap(err, "invalid sparse merkle trie")
}
return trie, nil
}
func (m *SparseMerkleTrie) validate() error {
if len(m.branches) == 0 {
return errors.New("no branches")
}
if len(m.branches[len(m.branches)-1]) == 0 {
return errors.New("invalid branches provided")
}
if m.depth >= uint(len(m.branches)) {
return errors.New("depth is greater than or equal to number of branches")
}
if m.depth >= 64 {
return errors.New("depth exceeds 64") // PowerOf2 would overflow.
}
return nil
} }
// GenerateTrieFromItems constructs a Merkle trie from a sequence of byte slices. // GenerateTrieFromItems constructs a Merkle trie from a sequence of byte slices.
@ -82,10 +104,6 @@ func (m *SparseMerkleTrie) Items() [][]byte {
// Spec Definition: // Spec Definition:
// sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24))) // sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
func (m *SparseMerkleTrie) HashTreeRoot() ([32]byte, error) { func (m *SparseMerkleTrie) HashTreeRoot() ([32]byte, error) {
if len(m.branches) == 0 || len(m.branches[len(m.branches)-1]) == 0 {
return [32]byte{}, errors.New("invalid branches provided to compute root")
}
enc := [32]byte{} enc := [32]byte{}
depositCount := uint64(len(m.originalItems)) depositCount := uint64(len(m.originalItems))
if len(m.originalItems) == 1 && bytes.Equal(m.originalItems[0], ZeroHashes[0][:]) { if len(m.originalItems) == 1 && bytes.Equal(m.originalItems[0], ZeroHashes[0][:]) {
@ -101,12 +119,6 @@ func (m *SparseMerkleTrie) Insert(item []byte, index int) error {
if index < 0 { if index < 0 {
return fmt.Errorf("negative index provided: %d", index) return fmt.Errorf("negative index provided: %d", index)
} }
if len(m.branches) == 0 {
return errors.New("invalid trie: no branches")
}
if m.depth > uint(len(m.branches)) {
return errors.New("invalid trie: depth is greater than number of branches")
}
for index >= len(m.branches[0]) { for index >= len(m.branches[0]) {
m.branches[0] = append(m.branches[0], ZeroHashes[0][:]) m.branches[0] = append(m.branches[0], ZeroHashes[0][:])
} }
@ -153,16 +165,10 @@ func (m *SparseMerkleTrie) MerkleProof(index int) ([][]byte, error) {
if index < 0 { if index < 0 {
return nil, fmt.Errorf("merkle index is negative: %d", index) return nil, fmt.Errorf("merkle index is negative: %d", index)
} }
if len(m.branches) == 0 {
return nil, errors.New("invalid trie: no branches")
}
leaves := m.branches[0] leaves := m.branches[0]
if index >= len(leaves) { if index >= len(leaves) {
return nil, fmt.Errorf("merkle index out of range in trie, max range: %d, received: %d", len(leaves), index) return nil, fmt.Errorf("merkle index out of range in trie, max range: %d, received: %d", len(leaves), index)
} }
if m.depth > uint(len(m.branches)) {
return nil, errors.New("invalid trie: depth is greater than number of branches")
}
merkleIndex := uint(index) merkleIndex := uint(index)
proof := make([][]byte, m.depth+1) proof := make([][]byte, m.depth+1)
for i := uint(0); i < m.depth; i++ { for i := uint(0); i < m.depth; i++ {

View File

@ -16,6 +16,68 @@ import (
"github.com/prysmaticlabs/prysm/testing/require" "github.com/prysmaticlabs/prysm/testing/require"
) )
func TestCreateTrieFromProto_Validation(t *testing.T) {
h := hash.Hash([]byte("hi"))
genValidLayers := func(num int) []*ethpb.TrieLayer {
l := make([]*ethpb.TrieLayer, num)
for i := 0; i < num; i++ {
l[i] = &ethpb.TrieLayer{
Layer: [][]byte{h[:]},
}
}
return l
}
tests := []struct {
trie *ethpb.SparseMerkleTrie
errString string
}{
{
trie: &ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{},
Depth: 0,
},
errString: "no branches",
},
{
trie: &ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{},
},
},
Depth: 2,
},
errString: "invalid branches provided",
},
{
trie: &ethpb.SparseMerkleTrie{
Layers: genValidLayers(3),
Depth: 12,
},
errString: "depth is greater than or equal to number of branches",
},
{
trie: &ethpb.SparseMerkleTrie{
Layers: genValidLayers(66),
Depth: 65,
},
errString: "depth exceeds 64",
},
}
for _, tt := range tests {
t.Run(tt.errString, func(t *testing.T) {
_, err := trie.CreateTrieFromProto(tt.trie)
require.ErrorContains(t, tt.errString, err)
})
}
}
func TestMarshalDepositWithProof(t *testing.T) { func TestMarshalDepositWithProof(t *testing.T) {
items := [][]byte{ items := [][]byte{
[]byte("A"), []byte("A"),
@ -52,7 +114,7 @@ func TestMarshalDepositWithProof(t *testing.T) {
func TestMerkleTrie_MerkleProofOutOfRange(t *testing.T) { func TestMerkleTrie_MerkleProofOutOfRange(t *testing.T) {
h := hash.Hash([]byte("hi")) h := hash.Hash([]byte("hi"))
m := trie.CreateTrieFromProto(&ethpb.SparseMerkleTrie{ m, err := trie.CreateTrieFromProto(&ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{ Layers: []*ethpb.TrieLayer{
{ {
Layer: [][]byte{h[:]}, Layer: [][]byte{h[:]},
@ -61,11 +123,12 @@ func TestMerkleTrie_MerkleProofOutOfRange(t *testing.T) {
Layer: [][]byte{h[:]}, Layer: [][]byte{h[:]},
}, },
{ {
Layer: [][]byte{}, Layer: [][]byte{h[:]},
}, },
}, },
Depth: 4, Depth: 2,
}) })
require.NoError(t, err)
if _, err := m.MerkleProof(6); err == nil { if _, err := m.MerkleProof(6); err == nil {
t.Error("Expected out of range failure, received nil", err) t.Error("Expected out of range failure, received nil", err)
} }
@ -128,6 +191,7 @@ func TestMerkleTrie_VerifyMerkleProof(t *testing.T) {
[]byte("G"), []byte("G"),
[]byte("H"), []byte("H"),
} }
m, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth) m, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth)
require.NoError(t, err) require.NoError(t, err)
proof, err := m.MerkleProof(0) proof, err := m.MerkleProof(0)
@ -209,7 +273,8 @@ func TestRoundtripProto_OK(t *testing.T) {
depositRoot, err := m.HashTreeRoot() depositRoot, err := m.HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
newTrie := trie.CreateTrieFromProto(protoTrie) newTrie, err := trie.CreateTrieFromProto(protoTrie)
require.NoError(t, err)
root, err := newTrie.HashTreeRoot() root, err := newTrie.HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
require.DeepEqual(t, depositRoot, root) require.DeepEqual(t, depositRoot, root)

View File

@ -22,10 +22,10 @@ func FuzzSparseMerkleTrie_HashTreeRoot(f *testing.F) {
Layer: [][]byte{h[:]}, Layer: [][]byte{h[:]},
}, },
{ {
Layer: [][]byte{}, Layer: [][]byte{h[:]},
}, },
}, },
Depth: 4, Depth: 2,
} }
b, err := proto.Marshal(pb) b, err := proto.Marshal(pb)
require.NoError(f, err) require.NoError(f, err)
@ -36,10 +36,13 @@ func FuzzSparseMerkleTrie_HashTreeRoot(f *testing.F) {
if err := proto.Unmarshal(b, pb); err != nil { if err := proto.Unmarshal(b, pb); err != nil {
return return
} }
_, err := trie.CreateTrieFromProto(pb).HashTreeRoot() smt, err := trie.CreateTrieFromProto(pb)
if err != nil { if err != nil {
return return
} }
if _, err := smt.HashTreeRoot(); err != nil {
return
}
}) })
} }
@ -54,10 +57,10 @@ func FuzzSparseMerkleTrie_MerkleProof(f *testing.F) {
Layer: [][]byte{h[:]}, Layer: [][]byte{h[:]},
}, },
{ {
Layer: [][]byte{}, Layer: [][]byte{h[:]},
}, },
}, },
Depth: 4, Depth: 2,
} }
b, err := proto.Marshal(pb) b, err := proto.Marshal(pb)
require.NoError(f, err) require.NoError(f, err)
@ -68,10 +71,13 @@ func FuzzSparseMerkleTrie_MerkleProof(f *testing.F) {
if err := proto.Unmarshal(b, pb); err != nil { if err := proto.Unmarshal(b, pb); err != nil {
return return
} }
_, err := trie.CreateTrieFromProto(pb).MerkleProof(i) smt, err := trie.CreateTrieFromProto(pb)
if err != nil { if err != nil {
return return
} }
if _, err := smt.MerkleProof(i); err != nil {
return
}
}) })
} }
@ -86,10 +92,10 @@ func FuzzSparseMerkleTrie_Insert(f *testing.F) {
Layer: [][]byte{h[:]}, Layer: [][]byte{h[:]},
}, },
{ {
Layer: [][]byte{}, Layer: [][]byte{h[:]},
}, },
}, },
Depth: 4, Depth: 2,
} }
b, err := proto.Marshal(pb) b, err := proto.Marshal(pb)
require.NoError(f, err) require.NoError(f, err)
@ -100,7 +106,11 @@ func FuzzSparseMerkleTrie_Insert(f *testing.F) {
if err := proto.Unmarshal(b, pb); err != nil { if err := proto.Unmarshal(b, pb); err != nil {
return return
} }
if err := trie.CreateTrieFromProto(pb).Insert(item, i); err != nil { smt, err := trie.CreateTrieFromProto(pb)
if err != nil {
return
}
if err := smt.Insert(item, i); err != nil {
return return
} }
}) })

View File

@ -0,0 +1,4 @@
go test fuzz v1
[]byte("\b\x03\x12\"2 00000000000000000000000000000000\x12\"2 00000000000000000000000000000000\x12\x00")
[]byte("")
int(0)