Skip to content

Commit

Permalink
Verify transactions in a block are well formed (release-2.2)
Browse files Browse the repository at this point in the history
Verify that transactions in blocks appear exactly as their marshaled form after unmarshaling.

Backport of hyperledger#4490

Signed-off-by: David Enyeart <enyeart@us.ibm.com>
  • Loading branch information
denyeart committed Oct 27, 2023
1 parent 5af7a85 commit 50ab42d
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions internal/peer/gossip/mcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ func (s *MSPMessageCryptoService) VerifyBlock(chainID common.ChannelID, seqNum u
return fmt.Errorf("Failed unmarshalling medatata for signatures [%s]", err)
}

if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil {
return err
}

// - Verify that Header.DataHash is equal to the hash of block.Data
// This is to ensure that the header is consistent with the data carried by this block
if !bytes.Equal(protoutil.BlockDataHash(block.Data), block.Header.DataHash) {
Expand Down
3 changes: 3 additions & 0 deletions orderer/common/cluster/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ func VerifyBlockHash(indexInBuffer int, blockBuff []*common.Block) error {
if block.Header == nil {
return errors.New("missing block header")
}
if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil {
return err
}
seq := block.Header.Number
dataHash := protoutil.BlockDataHash(block.Data)
// Verify data hash matches the hash in the header
Expand Down
38 changes: 38 additions & 0 deletions protoutil/blockutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"bytes"
"crypto/sha256"
"encoding/asn1"
"encoding/base64"
"fmt"
"math/big"

"github.com/golang/protobuf/proto"
Expand Down Expand Up @@ -218,3 +220,39 @@ func InitBlockMetadata(block *cb.Block) {
}
}
}

func VerifyTransactionsAreWellFormed(block *cb.Block) error {
if block == nil || block.Data == nil || len(block.Data.Data) == 0 {
return fmt.Errorf("empty block")
}

for i, rawTx := range block.Data.Data {
env := &cb.Envelope{}
if err := proto.Unmarshal(rawTx, env); err != nil {
return fmt.Errorf("transaction %d is invalid: %v", i, err)
}

if len(env.Payload) == 0 {
return fmt.Errorf("transaction %d has no payload", i)
}

if len(env.Signature) == 0 {
return fmt.Errorf("transaction %d has no signature", i)
}

expected, err := proto.Marshal(env)
if err != nil {
return fmt.Errorf("failed re-marshaling envelope: %v", err)
}

if len(expected) < len(rawTx) {
return fmt.Errorf("transaction %d has %d trailing bytes", i, len(rawTx)-len(expected))
}
if !bytes.Equal(expected, rawTx) {
return fmt.Errorf("transaction %d (%s) does not match its raw form (%s)", i,
base64.StdEncoding.EncodeToString(expected), base64.StdEncoding.EncodeToString(rawTx))
}
}

return nil
}
109 changes: 109 additions & 0 deletions protoutil/blockutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,112 @@ func TestGetLastConfigIndexFromBlock(t *testing.T) {
}, "Expected panic with malformed last config metadata")
})
}

func TestVerifyTransactionsAreWellFormed(t *testing.T) {
originalBlock := &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
marshalOrPanic(&cb.Envelope{
Payload: []byte{1, 2, 3},
Signature: []byte{4, 5, 6},
}),
marshalOrPanic(&cb.Envelope{
Payload: []byte{7, 8, 9},
Signature: []byte{10, 11, 12},
}),
},
},
}

forgedBlock := proto.Clone(originalBlock).(*cb.Block)
tmp := make([]byte, len(forgedBlock.Data.Data[0])+len(forgedBlock.Data.Data[1]))
copy(tmp, forgedBlock.Data.Data[0])
copy(tmp[len(forgedBlock.Data.Data[0]):], forgedBlock.Data.Data[1])
forgedBlock.Data.Data = [][]byte{tmp} // Replace transactions {0,1} with transaction {0 || 1}

for _, tst := range []struct {
name string
expectedError string
block *cb.Block
}{
{
name: "empty block",
expectedError: "empty block",
},
{
name: "no block data",
block: &cb.Block{},
expectedError: "empty block",
},
{
name: "no transactions",
block: &cb.Block{Data: &cb.BlockData{}},
expectedError: "empty block",
},
{
name: "single transaction",
block: &cb.Block{Data: &cb.BlockData{Data: [][]byte{marshalOrPanic(&cb.Envelope{
Payload: []byte{1, 2, 3},
Signature: []byte{4, 5, 6},
})}}},
},
{
name: "good block",
block: originalBlock,
},
{
name: "forged block",
block: forgedBlock,
expectedError: "transaction 0 has 10 trailing bytes",
},
{
name: "no signature",
expectedError: "transaction 0 has no signature",
block: &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
marshalOrPanic(&cb.Envelope{
Payload: []byte{1, 2, 3},
}),
},
},
},
},
{
name: "no payload",
expectedError: "transaction 0 has no payload",
block: &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
marshalOrPanic(&cb.Envelope{
Signature: []byte{4, 5, 6},
}),
},
},
},
},
{
name: "transaction invalid",
expectedError: "illegal tag 0 (wire type 6)",
block: &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
marshalOrPanic(&cb.Envelope{
Payload: []byte{1, 2, 3},
Signature: []byte{4, 5, 6},
})[9:],
},
},
},
},
} {
t.Run(tst.name, func(t *testing.T) {
err := protoutil.VerifyTransactionsAreWellFormed(tst.block)
if tst.expectedError == "" {
require.NoError(t, err)
} else {
require.Contains(t, err.Error(), tst.expectedError)
}
})
}
}

0 comments on commit 50ab42d

Please sign in to comment.