Skip to content

Commit b888828

Browse files
committed
Verify transactions in a block are well formed
Verify that transactions in blocks appear exactly as their marshaled form after unmarshaling. Signed-off-by: Yacov Manevich <[email protected]>
1 parent c3b8221 commit b888828

File tree

7 files changed

+200
-10
lines changed

7 files changed

+200
-10
lines changed

internal/peer/gossip/mcs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func (s *MSPMessageCryptoService) VerifyBlock(chainID common.ChannelID, seqNum u
150150
return fmt.Errorf("Failed unmarshalling medatata for signatures [%s]", err)
151151
}
152152

153+
if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil {
154+
return fmt.Errorf("block has malformed transactions: %v", err)
155+
}
156+
153157
// - Verify that Header.DataHash is equal to the hash of block.Data
154158
// This is to ensure that the header is consistent with the data carried by this block
155159
if !bytes.Equal(protoutil.BlockDataHash(block.Data), block.Header.DataHash) {

orderer/common/cluster/replication_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func TestReplicateChainsFailures(t *testing.T) {
130130
name: "hash chain mismatch",
131131
expectedPanic: "Failed pulling system channel: " +
132132
"block header mismatch on sequence 11, " +
133-
"expected 9cd61b7e9a5ea2d128cc877e5304e7205888175a8032d40b97db7412dca41d9e, got 010203",
133+
"expected 229de8d87db1ddf7278093bc65ba9245cd3acd8ccfc687e0edc68adf9b181488, got 010203",
134134
latestBlockSeqInOrderer: 21,
135135
mutateBlocks: func(systemChannelBlocks []*common.Block) {
136136
systemChannelBlocks[len(systemChannelBlocks)/2].Header.PreviousHash = []byte{1, 2, 3}
@@ -139,8 +139,8 @@ func TestReplicateChainsFailures(t *testing.T) {
139139
{
140140
name: "last pulled block doesn't match the boot block",
141141
expectedPanic: "Block header mismatch on last system channel block," +
142-
" expected 8ec93b2ef5ffdc302f0c0e24611be04ad2b17b099a1aeafd7cfb76a95923f146," +
143-
" got e428decfc78f8e4c97b26da9c16f9d0b73f886dafa80477a0dd9bac7eb14fe7a",
142+
" expected 0bea18bff7feeaa0bc08f528e1f563c818fc0633e291786c96eedffd8c7e6cff," +
143+
" got 924c568c4a4e8f16e3cbd123ea0ae38d0bbb01d60bafb74f4bb55f108c0eb194",
144144
latestBlockSeqInOrderer: 21,
145145
mutateBlocks: func(systemChannelBlocks []*common.Block) {
146146
systemChannelBlocks[21].Header.DataHash = nil

orderer/common/cluster/util.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ func VerifyBlockHash(indexInBuffer int, blockBuff []*common.Block) error {
297297
return errors.New("missing block header")
298298
}
299299
seq := block.Header.Number
300+
301+
if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil && block.Header.Number > 0 {
302+
return fmt.Errorf("block has malformed transactions: %v", err)
303+
}
304+
300305
dataHash := protoutil.BlockDataHash(block.Data)
301306
// Verify data hash matches the hash in the header
302307
if !bytes.Equal(dataHash, block.Header.DataHash) {

orderer/common/cluster/util_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func TestVerifyBlockHash(t *testing.T) {
285285
},
286286
{
287287
name: "data hash mismatch",
288-
errorContains: "computed hash of block (13) (dcb2ec1c5e482e4914cb953ff8eedd12774b244b12912afbe6001ba5de9ff800)" +
288+
errorContains: "computed hash of block (13) (6de668ac99645e179a4921b477d50df9295fa56cd44f8e5c94756b60ce32ce1c)" +
289289
" doesn't match claimed hash (07)",
290290
mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block {
291291
blockSequence[len(blockSequence)/2].Header.DataHash = []byte{7}
@@ -295,7 +295,7 @@ func TestVerifyBlockHash(t *testing.T) {
295295
{
296296
name: "prev hash mismatch",
297297
errorContains: "block [12]'s hash " +
298-
"(866351705f1c2f13e10d52ead9d0ca3b80689ede8cc8bf70a6d60c67578323f4) " +
298+
"(72cc7ddf4d8465da95115c0a906416d23d8c74bfcb731a5ab057c213d8db62e1) " +
299299
"mismatches block [13]'s prev block hash (07)",
300300
mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block {
301301
blockSequence[len(blockSequence)/2].Header.PreviousHash = []byte{7}
@@ -369,7 +369,7 @@ func TestVerifyBlocks(t *testing.T) {
369369
return blockSequence
370370
},
371371
expectedError: "block [74]'s hash " +
372-
"(5cb4bd1b6a73f81afafd96387bb7ff4473c2425929d0862586f5fbfa12d762dd) " +
372+
"(6daec1924ac6db2b23e3f49c190115dfc096603bcd0ec916baf111c68633c969) " +
373373
"mismatches block [75]'s prev block hash (07)",
374374
},
375375
{
@@ -394,7 +394,7 @@ func TestVerifyBlocks(t *testing.T) {
394394
assignHashes(blockSequence)
395395
return blockSequence
396396
},
397-
expectedError: "nil header in payload",
397+
expectedError: "block has malformed transactions: transaction 0 has no payload",
398398
},
399399
{
400400
name: "config blocks in the sequence need to be verified and one of them is improperly signed",
@@ -546,6 +546,7 @@ func createBlockChain(start, end uint64) []*common.Block {
546546
})
547547

548548
txn := protoutil.MarshalOrPanic(&common.Envelope{
549+
Signature: []byte{1, 2, 3},
549550
Payload: protoutil.MarshalOrPanic(&common.Payload{
550551
Header: &common.Header{},
551552
}),
@@ -554,9 +555,8 @@ func createBlockChain(start, end uint64) []*common.Block {
554555
return block
555556
}
556557
var blockchain []*common.Block
557-
for seq := uint64(start); seq <= uint64(end); seq++ {
558+
for seq := start; seq <= end; seq++ {
558559
block := newBlock(seq)
559-
block.Data.Data = append(block.Data.Data, make([]byte, 100))
560560
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
561561
blockchain = append(blockchain, block)
562562
}

protoutil/blockutils.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"bytes"
1111
"crypto/sha256"
1212
"encoding/asn1"
13+
"encoding/base64"
14+
"fmt"
1315
"math/big"
1416

1517
"github.com/golang/protobuf/proto"
@@ -218,3 +220,45 @@ func InitBlockMetadata(block *cb.Block) {
218220
}
219221
}
220222
}
223+
224+
func VerifyTransactionsAreWellFormed(block *cb.Block) error {
225+
if block == nil || block.Data == nil || len(block.Data.Data) == 0 {
226+
return fmt.Errorf("empty block")
227+
}
228+
229+
// If we have a single transaction, and the block is a config block, then no need to check
230+
// well formed-ness, because there cannot be another transaction in the original block.
231+
if IsConfigBlock(block) {
232+
return nil
233+
}
234+
235+
for i, rawTx := range block.Data.Data {
236+
env := &cb.Envelope{}
237+
if err := proto.Unmarshal(rawTx, env); err != nil {
238+
return fmt.Errorf("transaction %d is invalid: %v", i, err)
239+
}
240+
241+
if len(env.Payload) == 0 {
242+
return fmt.Errorf("transaction %d has no payload", i)
243+
}
244+
245+
if len(env.Signature) == 0 {
246+
return fmt.Errorf("transaction %d has no signature", i)
247+
}
248+
249+
expected, err := proto.Marshal(env)
250+
if err != nil {
251+
return fmt.Errorf("failed re-marshaling envelope: %v", err)
252+
}
253+
254+
if len(expected) < len(rawTx) {
255+
return fmt.Errorf("transaction %d has %d trailing bytes", i, len(rawTx)-len(expected))
256+
}
257+
if !bytes.Equal(expected, rawTx) {
258+
return fmt.Errorf("transaction %d (%s) does not match its raw form (%s)", i,
259+
base64.StdEncoding.EncodeToString(expected), base64.StdEncoding.EncodeToString(rawTx))
260+
}
261+
}
262+
263+
return nil
264+
}

protoutil/blockutils_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,3 +374,131 @@ func TestGetLastConfigIndexFromBlock(t *testing.T) {
374374
}, "Expected panic with malformed last config metadata")
375375
})
376376
}
377+
378+
func TestVerifyTransactionsAreWellFormed(t *testing.T) {
379+
originalBlock := &cb.Block{
380+
Data: &cb.BlockData{
381+
Data: [][]byte{
382+
marshalOrPanic(&cb.Envelope{
383+
Payload: []byte{1, 2, 3},
384+
Signature: []byte{4, 5, 6},
385+
}),
386+
marshalOrPanic(&cb.Envelope{
387+
Payload: []byte{7, 8, 9},
388+
Signature: []byte{10, 11, 12},
389+
}),
390+
},
391+
},
392+
}
393+
394+
forgedBlock := proto.Clone(originalBlock).(*cb.Block)
395+
tmp := make([]byte, len(forgedBlock.Data.Data[0])+len(forgedBlock.Data.Data[1]))
396+
copy(tmp, forgedBlock.Data.Data[0])
397+
copy(tmp[len(forgedBlock.Data.Data[0]):], forgedBlock.Data.Data[1])
398+
forgedBlock.Data.Data = [][]byte{tmp} // Replace transactions {0,1} with transaction {0 || 1}
399+
400+
for _, tst := range []struct {
401+
name string
402+
expectedError string
403+
block *cb.Block
404+
}{
405+
{
406+
name: "config block",
407+
block: &cb.Block{Data: &cb.BlockData{
408+
Data: [][]byte{
409+
protoutil.MarshalOrPanic(
410+
&cb.Envelope{
411+
Payload: protoutil.MarshalOrPanic(&cb.Payload{
412+
Header: &cb.Header{
413+
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
414+
Type: int32(cb.HeaderType_CONFIG),
415+
}),
416+
},
417+
}),
418+
}),
419+
},
420+
}},
421+
},
422+
{
423+
name: "empty block",
424+
expectedError: "empty block",
425+
},
426+
{
427+
name: "no block data",
428+
block: &cb.Block{},
429+
expectedError: "empty block",
430+
},
431+
{
432+
name: "no transactions",
433+
block: &cb.Block{Data: &cb.BlockData{}},
434+
expectedError: "empty block",
435+
},
436+
{
437+
name: "single transaction",
438+
block: &cb.Block{Data: &cb.BlockData{Data: [][]byte{marshalOrPanic(&cb.Envelope{
439+
Payload: []byte{1, 2, 3},
440+
Signature: []byte{4, 5, 6},
441+
})}}},
442+
},
443+
444+
{
445+
name: "good block",
446+
block: originalBlock,
447+
},
448+
{
449+
name: "forged block",
450+
block: forgedBlock,
451+
expectedError: "transaction 0 has 10 trailing bytes",
452+
},
453+
{
454+
name: "no signature",
455+
expectedError: "transaction 0 has no signature",
456+
block: &cb.Block{
457+
Data: &cb.BlockData{
458+
Data: [][]byte{
459+
marshalOrPanic(&cb.Envelope{
460+
Payload: []byte{1, 2, 3},
461+
}),
462+
},
463+
},
464+
},
465+
},
466+
{
467+
name: "no payload",
468+
expectedError: "transaction 0 has no payload",
469+
block: &cb.Block{
470+
Data: &cb.BlockData{
471+
Data: [][]byte{
472+
marshalOrPanic(&cb.Envelope{
473+
Signature: []byte{4, 5, 6},
474+
}),
475+
},
476+
},
477+
},
478+
},
479+
{
480+
name: "transaction invalid",
481+
expectedError: "cannot parse invalid wire-format data",
482+
block: &cb.Block{
483+
Data: &cb.BlockData{
484+
Data: [][]byte{
485+
marshalOrPanic(&cb.Envelope{
486+
Payload: []byte{1, 2, 3},
487+
Signature: []byte{4, 5, 6},
488+
})[9:],
489+
},
490+
},
491+
},
492+
},
493+
} {
494+
tst := tst
495+
t.Run(tst.name, func(t *testing.T) {
496+
err := protoutil.VerifyTransactionsAreWellFormed(tst.block)
497+
if tst.expectedError == "" {
498+
require.NoError(t, err)
499+
} else {
500+
require.Contains(t, err.Error(), tst.expectedError)
501+
}
502+
})
503+
}
504+
}

protoutil/commonutils.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,16 @@ func SignOrPanic(signer identity.Signer, msg []byte) []byte {
188188
// IsConfigBlock validates whenever given block contains configuration
189189
// update transaction
190190
func IsConfigBlock(block *cb.Block) bool {
191-
envelope, err := ExtractEnvelope(block, 0)
191+
if block.Data == nil {
192+
return false
193+
}
194+
195+
if len(block.Data.Data) != 1 {
196+
return false
197+
}
198+
199+
marshaledEnvelope := block.Data.Data[0]
200+
envelope, err := GetEnvelopeFromBlock(marshaledEnvelope)
192201
if err != nil {
193202
return false
194203
}

0 commit comments

Comments
 (0)