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}
|
||||
}
|
||||
|
||||
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 {
|
||||
var eb extblock
|
||||
if err := s.Decode(&eb); err != nil {
|
||||
|
@ -105,7 +105,7 @@ type getBlockHashesMsgData struct {
|
||||
type statusMsgData struct {
|
||||
ProtocolVersion uint32
|
||||
NetworkId uint32
|
||||
TD *big.Int
|
||||
TD big.Int
|
||||
CurrentBlock common.Hash
|
||||
GenesisBlock common.Hash
|
||||
}
|
||||
@ -276,6 +276,9 @@ func (self *ethProtocol) handle() error {
|
||||
if err := msg.Decode(&request); err != nil {
|
||||
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()
|
||||
_, chainHead, _ := self.chainManager.Status()
|
||||
|
||||
@ -335,7 +338,7 @@ func (self *ethProtocol) handleStatus() error {
|
||||
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 {
|
||||
return self.protoError(ErrSuspendedPeer, "")
|
||||
}
|
||||
@ -366,7 +369,7 @@ func (self *ethProtocol) sendStatus() error {
|
||||
return p2p.Send(self.rw, StatusMsg, &statusMsgData{
|
||||
ProtocolVersion: uint32(self.protocolVersion),
|
||||
NetworkId: uint32(self.networkId),
|
||||
TD: td,
|
||||
TD: *td,
|
||||
CurrentBlock: currentBlock,
|
||||
GenesisBlock: genesisBlock,
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"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) {
|
||||
if self.status != nil {
|
||||
td, currentBlock, genesisBlock = self.status()
|
||||
} else {
|
||||
td = common.Big1
|
||||
currentBlock = common.Hash{1}
|
||||
genesisBlock = common.Hash{2}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -163,14 +168,29 @@ func (self *ethProtocolTester) run() {
|
||||
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) {
|
||||
logInit()
|
||||
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()
|
||||
td, currentBlock, genesis := eth.chainManager.Status()
|
||||
|
||||
tests := []struct {
|
||||
code uint64
|
||||
@ -182,31 +202,20 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||
wantErrorCode: ErrNoStatusMsg,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusMsgData{10, NetworkId, td, currentBlock, genesis},
|
||||
code: StatusMsg, data: statusMsgData{10, NetworkId, *td, currentBlock, genesis},
|
||||
wantErrorCode: ErrProtocolVersionMismatch,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusMsgData{ProtocolVersion, 999, td, currentBlock, genesis},
|
||||
code: StatusMsg, data: statusMsgData{ProtocolVersion, 999, *td, currentBlock, genesis},
|
||||
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,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// first outgoing msg should be StatusMsg.
|
||||
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)
|
||||
}
|
||||
|
||||
eth.handshake(t, false)
|
||||
// the send call might hang until reset because
|
||||
// the protocol might not read the payload.
|
||||
go p2p.Send(eth, test.code, test.data)
|
||||
@ -216,3 +225,73 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||
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
Block a user