mirror of https://github.com/status-im/op-geth.git
eth: SEC-29 eth wire protocol decoding invalid message data crashes client
- add validate method to types.Block - validate after Decode -> error - add tests for NewBlockMsg
This commit is contained in:
parent
936ddf2ad1
commit
e1be34bce1
|
@ -148,6 +148,26 @@ func NewBlockWithHeader(header *Header) *Block {
|
||||||
return &Block{header: header}
|
return &Block{header: header}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Block) Validate() error {
|
||||||
|
if self.header == nil {
|
||||||
|
return fmt.Errorf("header is nil")
|
||||||
|
}
|
||||||
|
// check *big.Int fields
|
||||||
|
if self.header.Difficulty == nil {
|
||||||
|
return fmt.Errorf("Difficulty undefined")
|
||||||
|
}
|
||||||
|
if self.header.GasLimit == nil {
|
||||||
|
return fmt.Errorf("GasLimit undefined")
|
||||||
|
}
|
||||||
|
if self.header.GasUsed == nil {
|
||||||
|
return fmt.Errorf("GasUsed undefined")
|
||||||
|
}
|
||||||
|
if self.header.Number == nil {
|
||||||
|
return fmt.Errorf("Number undefined")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Block) DecodeRLP(s *rlp.Stream) error {
|
func (self *Block) DecodeRLP(s *rlp.Stream) error {
|
||||||
var eb extblock
|
var eb extblock
|
||||||
if err := s.Decode(&eb); err != nil {
|
if err := s.Decode(&eb); err != nil {
|
||||||
|
|
|
@ -105,7 +105,7 @@ type getBlockHashesMsgData struct {
|
||||||
type statusMsgData struct {
|
type statusMsgData struct {
|
||||||
ProtocolVersion uint32
|
ProtocolVersion uint32
|
||||||
NetworkId uint32
|
NetworkId uint32
|
||||||
TD *big.Int
|
TD big.Int
|
||||||
CurrentBlock common.Hash
|
CurrentBlock common.Hash
|
||||||
GenesisBlock common.Hash
|
GenesisBlock common.Hash
|
||||||
}
|
}
|
||||||
|
@ -276,6 +276,9 @@ func (self *ethProtocol) handle() error {
|
||||||
if err := msg.Decode(&request); err != nil {
|
if err := msg.Decode(&request); err != nil {
|
||||||
return self.protoError(ErrDecode, "%v: %v", msg, err)
|
return self.protoError(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
if err := request.Block.Validate(); err != nil {
|
||||||
|
return self.protoError(ErrDecode, "block validation %v: %v", msg, err)
|
||||||
|
}
|
||||||
hash := request.Block.Hash()
|
hash := request.Block.Hash()
|
||||||
_, chainHead, _ := self.chainManager.Status()
|
_, chainHead, _ := self.chainManager.Status()
|
||||||
|
|
||||||
|
@ -335,7 +338,7 @@ func (self *ethProtocol) handleStatus() error {
|
||||||
return self.protoError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, self.protocolVersion)
|
return self.protoError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, self.protocolVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, suspended := self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect)
|
_, suspended := self.blockPool.AddPeer(&status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect)
|
||||||
if suspended {
|
if suspended {
|
||||||
return self.protoError(ErrSuspendedPeer, "")
|
return self.protoError(ErrSuspendedPeer, "")
|
||||||
}
|
}
|
||||||
|
@ -366,7 +369,7 @@ func (self *ethProtocol) sendStatus() error {
|
||||||
return p2p.Send(self.rw, StatusMsg, &statusMsgData{
|
return p2p.Send(self.rw, StatusMsg, &statusMsgData{
|
||||||
ProtocolVersion: uint32(self.protocolVersion),
|
ProtocolVersion: uint32(self.protocolVersion),
|
||||||
NetworkId: uint32(self.networkId),
|
NetworkId: uint32(self.networkId),
|
||||||
TD: td,
|
TD: *td,
|
||||||
CurrentBlock: currentBlock,
|
CurrentBlock: currentBlock,
|
||||||
GenesisBlock: genesisBlock,
|
GenesisBlock: genesisBlock,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
@ -63,6 +64,10 @@ func (self *testChainManager) GetBlockHashesFromHash(hash common.Hash, amount ui
|
||||||
func (self *testChainManager) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
|
func (self *testChainManager) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
|
||||||
if self.status != nil {
|
if self.status != nil {
|
||||||
td, currentBlock, genesisBlock = self.status()
|
td, currentBlock, genesisBlock = self.status()
|
||||||
|
} else {
|
||||||
|
td = common.Big1
|
||||||
|
currentBlock = common.Hash{1}
|
||||||
|
genesisBlock = common.Hash{2}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -163,14 +168,29 @@ func (self *ethProtocolTester) run() {
|
||||||
self.quit <- err
|
self.quit <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *ethProtocolTester) handshake(t *testing.T, mock bool) {
|
||||||
|
td, currentBlock, genesis := self.chainManager.Status()
|
||||||
|
// first outgoing msg should be StatusMsg.
|
||||||
|
err := p2p.ExpectMsg(self, StatusMsg, &statusMsgData{
|
||||||
|
ProtocolVersion: ProtocolVersion,
|
||||||
|
NetworkId: NetworkId,
|
||||||
|
TD: *td,
|
||||||
|
CurrentBlock: currentBlock,
|
||||||
|
GenesisBlock: genesis,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("incorrect outgoing status: %v", err)
|
||||||
|
}
|
||||||
|
if mock {
|
||||||
|
go p2p.Send(self, StatusMsg, &statusMsgData{ProtocolVersion, NetworkId, *td, currentBlock, genesis})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStatusMsgErrors(t *testing.T) {
|
func TestStatusMsgErrors(t *testing.T) {
|
||||||
logInit()
|
logInit()
|
||||||
eth := newEth(t)
|
eth := newEth(t)
|
||||||
td := common.Big1
|
|
||||||
currentBlock := common.Hash{1}
|
|
||||||
genesis := common.Hash{2}
|
|
||||||
eth.chainManager.status = func() (*big.Int, common.Hash, common.Hash) { return td, currentBlock, genesis }
|
|
||||||
go eth.run()
|
go eth.run()
|
||||||
|
td, currentBlock, genesis := eth.chainManager.Status()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
code uint64
|
code uint64
|
||||||
|
@ -182,31 +202,20 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||||
wantErrorCode: ErrNoStatusMsg,
|
wantErrorCode: ErrNoStatusMsg,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: StatusMsg, data: statusMsgData{10, NetworkId, td, currentBlock, genesis},
|
code: StatusMsg, data: statusMsgData{10, NetworkId, *td, currentBlock, genesis},
|
||||||
wantErrorCode: ErrProtocolVersionMismatch,
|
wantErrorCode: ErrProtocolVersionMismatch,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: StatusMsg, data: statusMsgData{ProtocolVersion, 999, td, currentBlock, genesis},
|
code: StatusMsg, data: statusMsgData{ProtocolVersion, 999, *td, currentBlock, genesis},
|
||||||
wantErrorCode: ErrNetworkIdMismatch,
|
wantErrorCode: ErrNetworkIdMismatch,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: StatusMsg, data: statusMsgData{ProtocolVersion, NetworkId, td, currentBlock, common.Hash{3}},
|
code: StatusMsg, data: statusMsgData{ProtocolVersion, NetworkId, *td, currentBlock, common.Hash{3}},
|
||||||
wantErrorCode: ErrGenesisBlockMismatch,
|
wantErrorCode: ErrGenesisBlockMismatch,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
// first outgoing msg should be StatusMsg.
|
eth.handshake(t, false)
|
||||||
err := p2p.ExpectMsg(eth, StatusMsg, &statusMsgData{
|
|
||||||
ProtocolVersion: ProtocolVersion,
|
|
||||||
NetworkId: NetworkId,
|
|
||||||
TD: td,
|
|
||||||
CurrentBlock: currentBlock,
|
|
||||||
GenesisBlock: genesis,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("incorrect outgoing status: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the send call might hang until reset because
|
// the send call might hang until reset because
|
||||||
// the protocol might not read the payload.
|
// the protocol might not read the payload.
|
||||||
go p2p.Send(eth, test.code, test.data)
|
go p2p.Send(eth, test.code, test.data)
|
||||||
|
@ -216,3 +225,73 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||||
go eth.run()
|
go eth.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewBlockMsg(t *testing.T) {
|
||||||
|
logInit()
|
||||||
|
eth := newEth(t)
|
||||||
|
eth.blockPool.addBlock = func(block *types.Block, peerId string) (err error) {
|
||||||
|
fmt.Printf("Add Block: %v\n", block)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var disconnected bool
|
||||||
|
eth.blockPool.removePeer = func(peerId string) {
|
||||||
|
fmt.Printf("peer <%s> is disconnected\n", peerId)
|
||||||
|
disconnected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
go eth.run()
|
||||||
|
|
||||||
|
eth.handshake(t, true)
|
||||||
|
err := p2p.ExpectMsg(eth, TxMsg, []interface{}{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("transactions expected, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tds = make(chan *big.Int)
|
||||||
|
eth.blockPool.addPeer = func(td *big.Int, currentBlock common.Hash, peerId string, requestHashes func(common.Hash) error, requestBlocks func([]common.Hash) error, peerError func(*errs.Error)) (best bool, suspended bool) {
|
||||||
|
tds <- td
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var delay = 1 * time.Second
|
||||||
|
// eth.reset()
|
||||||
|
block := types.NewBlock(common.Hash{1}, common.Address{1}, common.Hash{1}, common.Big1, 1, "extra")
|
||||||
|
|
||||||
|
go p2p.Send(eth, NewBlockMsg, &newBlockMsgData{Block: block})
|
||||||
|
timer := time.After(delay)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case td := <-tds:
|
||||||
|
if td.Cmp(common.Big0) != 0 {
|
||||||
|
t.Errorf("incorrect td %v, expected %v", td, common.Big0)
|
||||||
|
}
|
||||||
|
case <-timer:
|
||||||
|
t.Errorf("no td recorded after %v", delay)
|
||||||
|
return
|
||||||
|
case err := <-eth.quit:
|
||||||
|
t.Errorf("no error expected, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go p2p.Send(eth, NewBlockMsg, &newBlockMsgData{block, common.Big2})
|
||||||
|
timer = time.After(delay)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case td := <-tds:
|
||||||
|
if td.Cmp(common.Big2) != 0 {
|
||||||
|
t.Errorf("incorrect td %v, expected %v", td, common.Big2)
|
||||||
|
}
|
||||||
|
case <-timer:
|
||||||
|
t.Errorf("no td recorded after %v", delay)
|
||||||
|
return
|
||||||
|
case err := <-eth.quit:
|
||||||
|
t.Errorf("no error expected, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go p2p.Send(eth, NewBlockMsg, []interface{}{})
|
||||||
|
// Block.DecodeRLP: validation failed: header is nil
|
||||||
|
eth.checkError(ErrDecode, delay)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue