Crosslinking transaction attack in hyperledger/fabric
Description
Hyperledger Fabric is an open source permissioned distributed ledger framework. Combining two molecules to one another, called "cross-linking" results in a molecule with a chemical formula that is composed of all atoms of the original two molecules. In Fabric, one can take a block of transactions and cross-link the transactions in a way that alters the way the peers parse the transactions. If a first peer receives a block B and a second peer receives a block identical to B but with the transactions being cross-linked, the second peer will parse transactions in a different way and thus its world state will deviate from the first peer. Orderers or peers cannot detect that a block has its transactions cross-linked, because there is a vulnerability in the way Fabric hashes the transactions of blocks. It simply and naively concatenates them, which is insecure and lets an adversary craft a "cross-linked block" (block with cross-linked transactions) which alters the way peers process transactions. For example, it is possible to select a transaction and manipulate a peer to completely avoid processing it, without changing the computed hash of the block. Additional validations have been added in v2.2.14 and v2.5.5 to detect potential cross-linking issues before processing blocks. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/hyperledger/fabricGo | >= 1.0.0-alpha, < 2.2.14 | 2.2.14 |
github.com/hyperledger/fabricGo | >= 2.3.0, < 2.5.5 | 2.5.5 |
Affected products
1- Range: >= 1.0.0, < 2.2.14
Patches
2389b2e66de9aVerify transactions in a block are well formed
7 files changed · +211 −18
internal/peer/gossip/mcs.go+4 −1 modified@@ -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 fmt.Errorf("block has malformed transactions: %v", 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) { @@ -259,7 +263,6 @@ func (s *MSPMessageCryptoService) Expiration(peerIdentity api.PeerIdentityType) return time.Time{}, errors.Wrap(err, "Unable to extract msp.Identity from peer Identity") } return id.ExpiresAt(), nil - } func (s *MSPMessageCryptoService) getValidatedIdentity(peerIdentity api.PeerIdentityType) (msp.Identity, common.ChannelID, error) {
orderer/common/cluster/replication_test.go+7 −6 modified@@ -130,7 +130,7 @@ func TestReplicateChainsFailures(t *testing.T) { name: "hash chain mismatch", expectedPanic: "Failed pulling system channel: " + "block header mismatch on sequence 11, " + - "expected 9cd61b7e9a5ea2d128cc877e5304e7205888175a8032d40b97db7412dca41d9e, got 010203", + "expected 229de8d87db1ddf7278093bc65ba9245cd3acd8ccfc687e0edc68adf9b181488, got 010203", latestBlockSeqInOrderer: 21, mutateBlocks: func(systemChannelBlocks []*common.Block) { systemChannelBlocks[len(systemChannelBlocks)/2].Header.PreviousHash = []byte{1, 2, 3} @@ -139,8 +139,8 @@ func TestReplicateChainsFailures(t *testing.T) { { name: "last pulled block doesn't match the boot block", expectedPanic: "Block header mismatch on last system channel block," + - " expected 8ec93b2ef5ffdc302f0c0e24611be04ad2b17b099a1aeafd7cfb76a95923f146," + - " got e428decfc78f8e4c97b26da9c16f9d0b73f886dafa80477a0dd9bac7eb14fe7a", + " expected 0bea18bff7feeaa0bc08f528e1f563c818fc0633e291786c96eedffd8c7e6cff," + + " got 924c568c4a4e8f16e3cbd123ea0ae38d0bbb01d60bafb74f4bb55f108c0eb194", latestBlockSeqInOrderer: 21, mutateBlocks: func(systemChannelBlocks []*common.Block) { systemChannelBlocks[21].Header.DataHash = nil @@ -321,7 +321,6 @@ func TestPullChannelFailure(t *testing.T) { assert.Equal(t, cluster.ErrRetryCountExhausted, err) }) } - } func TestPullerConfigFromTopLevelConfig(t *testing.T) { @@ -443,8 +442,10 @@ func TestReplicateChainsGreenPath(t *testing.T) { channelLister := &mocks.ChannelLister{} channelLister.On("Channels").Return([]cluster.ChannelGenesisBlock{ {ChannelName: "E", GenesisBlock: fakeGB}, - {ChannelName: "D", GenesisBlock: fakeGB}, {ChannelName: "C", GenesisBlock: fakeGB}, - {ChannelName: "A", GenesisBlock: fakeGB}, {ChannelName: "B", GenesisBlock: fakeGB}, + {ChannelName: "D", GenesisBlock: fakeGB}, + {ChannelName: "C", GenesisBlock: fakeGB}, + {ChannelName: "A", GenesisBlock: fakeGB}, + {ChannelName: "B", GenesisBlock: fakeGB}, }) channelLister.On("Close")
orderer/common/cluster/util.go+5 −0 modified@@ -318,6 +318,11 @@ func VerifyBlockHash(indexInBuffer int, blockBuff []*common.Block) error { return errors.New("missing block header") } seq := block.Header.Number + + if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil && block.Header.Number > 0 { + return fmt.Errorf("block has malformed transactions: %v", err) + } + dataHash := protoutil.BlockDataHash(block.Data) // Verify data hash matches the hash in the header if !bytes.Equal(dataHash, block.Header.DataHash) {
orderer/common/cluster/util_test.go+12 −9 modified@@ -289,7 +289,7 @@ func TestVerifyBlockHash(t *testing.T) { }, { name: "data hash mismatch", - errorContains: "computed hash of block (13) (dcb2ec1c5e482e4914cb953ff8eedd12774b244b12912afbe6001ba5de9ff800)" + + errorContains: "computed hash of block (13) (6de668ac99645e179a4921b477d50df9295fa56cd44f8e5c94756b60ce32ce1c)" + " doesn't match claimed hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.DataHash = []byte{7} @@ -299,7 +299,7 @@ func TestVerifyBlockHash(t *testing.T) { { name: "prev hash mismatch", errorContains: "block [12]'s hash " + - "(866351705f1c2f13e10d52ead9d0ca3b80689ede8cc8bf70a6d60c67578323f4) " + + "(72cc7ddf4d8465da95115c0a906416d23d8c74bfcb731a5ab057c213d8db62e1) " + "mismatches block [13]'s prev block hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.PreviousHash = []byte{7} @@ -373,7 +373,7 @@ func TestVerifyBlocks(t *testing.T) { return blockSequence }, expectedError: "block [74]'s hash " + - "(5cb4bd1b6a73f81afafd96387bb7ff4473c2425929d0862586f5fbfa12d762dd) " + + "(6daec1924ac6db2b23e3f49c190115dfc096603bcd0ec916baf111c68633c969) " + "mismatches block [75]'s prev block hash (07)", }, { @@ -398,7 +398,7 @@ func TestVerifyBlocks(t *testing.T) { assignHashes(blockSequence) return blockSequence }, - expectedError: "nil header in payload", + expectedError: "block has malformed transactions: transaction 0 has no payload", }, { name: "config blocks in the sequence need to be verified and one of them is improperly signed", @@ -550,6 +550,7 @@ func createBlockChain(start, end uint64) []*common.Block { }) txn := protoutil.MarshalOrPanic(&common.Envelope{ + Signature: []byte{1, 2, 3}, Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: &common.Header{}, }), @@ -558,9 +559,8 @@ func createBlockChain(start, end uint64) []*common.Block { return block } var blockchain []*common.Block - for seq := uint64(start); seq <= uint64(end); seq++ { + for seq := start; seq <= end; seq++ { block := newBlock(seq) - block.Data.Data = append(block.Data.Data, make([]byte, 100)) block.Header.DataHash = protoutil.BlockDataHash(block.Data) blockchain = append(blockchain, block) } @@ -706,7 +706,8 @@ func TestConfigFromBlockBadInput(t *testing.T) { Payload: protoutil.MarshalOrPanic(&common.Payload{ Data: []byte{1, 2, 3}, }), - })}}}, + })}}, + }, }, { name: "invalid envelope in block", @@ -731,7 +732,8 @@ func TestConfigFromBlockBadInput(t *testing.T) { ChannelHeader: []byte{1, 2, 3}, }, }), - })}}}, + })}}, + }, }, { name: "invalid config block", @@ -747,7 +749,8 @@ func TestConfigFromBlockBadInput(t *testing.T) { }), }, }), - })}}}, + })}}, + }, }, } { t.Run(testCase.name, func(t *testing.T) {
protoutil/blockutils.go+44 −0 modified@@ -10,6 +10,8 @@ import ( "bytes" "crypto/sha256" "encoding/asn1" + "encoding/base64" + "fmt" "math/big" "github.com/golang/protobuf/proto" @@ -218,3 +220,45 @@ 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") + } + + // If we have a single transaction, and the block is a config block, then no need to check + // well formed-ness, because there cannot be another transaction in the original block. + if IsConfigBlock(block) { + return nil + } + + 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 +}
protoutil/blockutils_test.go+129 −1 modified@@ -210,7 +210,7 @@ func TestGetMetadataFromBlock(t *testing.T) { } func TestGetConsenterMetadataFromBlock(t *testing.T) { - var cases = []struct { + cases := []struct { name string value []byte signatures []byte @@ -376,3 +376,131 @@ 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: "config block", + block: &cb.Block{Data: &cb.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic( + &cb.Envelope{ + Payload: protoutil.MarshalOrPanic(&cb.Payload{ + Header: &cb.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{ + Type: int32(cb.HeaderType_CONFIG), + }), + }, + }), + }), + }, + }}, + }, + { + 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: "transaction 0 is invalid: proto: common.Envelope: 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:], + }, + }, + }, + }, + } { + tst := tst + 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) + } + }) + } +}
protoutil/commonutils.go+10 −1 modified@@ -188,7 +188,16 @@ func SignOrPanic(signer identity.Signer, msg []byte) []byte { // IsConfigBlock validates whenever given block contains configuration // update transaction func IsConfigBlock(block *cb.Block) bool { - envelope, err := ExtractEnvelope(block, 0) + if block.Data == nil { + return false + } + + if len(block.Data.Data) != 1 { + return false + } + + marshaledEnvelope := block.Data.Data[0] + envelope, err := GetEnvelopeFromBlock(marshaledEnvelope) if err != nil { return false }
93bef10bd3ceVerify transactions in a block are well formed
7 files changed · +200 −10
internal/peer/gossip/mcs.go+4 −0 modified@@ -150,6 +150,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 fmt.Errorf("block has malformed transactions: %v", 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) {
orderer/common/cluster/replication_test.go+3 −3 modified@@ -130,7 +130,7 @@ func TestReplicateChainsFailures(t *testing.T) { name: "hash chain mismatch", expectedPanic: "Failed pulling system channel: " + "block header mismatch on sequence 11, " + - "expected 9cd61b7e9a5ea2d128cc877e5304e7205888175a8032d40b97db7412dca41d9e, got 010203", + "expected 229de8d87db1ddf7278093bc65ba9245cd3acd8ccfc687e0edc68adf9b181488, got 010203", latestBlockSeqInOrderer: 21, mutateBlocks: func(systemChannelBlocks []*common.Block) { systemChannelBlocks[len(systemChannelBlocks)/2].Header.PreviousHash = []byte{1, 2, 3} @@ -139,8 +139,8 @@ func TestReplicateChainsFailures(t *testing.T) { { name: "last pulled block doesn't match the boot block", expectedPanic: "Block header mismatch on last system channel block," + - " expected 8ec93b2ef5ffdc302f0c0e24611be04ad2b17b099a1aeafd7cfb76a95923f146," + - " got e428decfc78f8e4c97b26da9c16f9d0b73f886dafa80477a0dd9bac7eb14fe7a", + " expected 0bea18bff7feeaa0bc08f528e1f563c818fc0633e291786c96eedffd8c7e6cff," + + " got 924c568c4a4e8f16e3cbd123ea0ae38d0bbb01d60bafb74f4bb55f108c0eb194", latestBlockSeqInOrderer: 21, mutateBlocks: func(systemChannelBlocks []*common.Block) { systemChannelBlocks[21].Header.DataHash = nil
orderer/common/cluster/util.go+5 −0 modified@@ -297,6 +297,11 @@ func VerifyBlockHash(indexInBuffer int, blockBuff []*common.Block) error { return errors.New("missing block header") } seq := block.Header.Number + + if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil && block.Header.Number > 0 { + return fmt.Errorf("block has malformed transactions: %v", err) + } + dataHash := protoutil.BlockDataHash(block.Data) // Verify data hash matches the hash in the header if !bytes.Equal(dataHash, block.Header.DataHash) {
orderer/common/cluster/util_test.go+6 −6 modified@@ -285,7 +285,7 @@ func TestVerifyBlockHash(t *testing.T) { }, { name: "data hash mismatch", - errorContains: "computed hash of block (13) (dcb2ec1c5e482e4914cb953ff8eedd12774b244b12912afbe6001ba5de9ff800)" + + errorContains: "computed hash of block (13) (6de668ac99645e179a4921b477d50df9295fa56cd44f8e5c94756b60ce32ce1c)" + " doesn't match claimed hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.DataHash = []byte{7} @@ -295,7 +295,7 @@ func TestVerifyBlockHash(t *testing.T) { { name: "prev hash mismatch", errorContains: "block [12]'s hash " + - "(866351705f1c2f13e10d52ead9d0ca3b80689ede8cc8bf70a6d60c67578323f4) " + + "(72cc7ddf4d8465da95115c0a906416d23d8c74bfcb731a5ab057c213d8db62e1) " + "mismatches block [13]'s prev block hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.PreviousHash = []byte{7} @@ -369,7 +369,7 @@ func TestVerifyBlocks(t *testing.T) { return blockSequence }, expectedError: "block [74]'s hash " + - "(5cb4bd1b6a73f81afafd96387bb7ff4473c2425929d0862586f5fbfa12d762dd) " + + "(6daec1924ac6db2b23e3f49c190115dfc096603bcd0ec916baf111c68633c969) " + "mismatches block [75]'s prev block hash (07)", }, { @@ -394,7 +394,7 @@ func TestVerifyBlocks(t *testing.T) { assignHashes(blockSequence) return blockSequence }, - expectedError: "nil header in payload", + expectedError: "block has malformed transactions: transaction 0 has no payload", }, { 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 { }) txn := protoutil.MarshalOrPanic(&common.Envelope{ + Signature: []byte{1, 2, 3}, Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: &common.Header{}, }), @@ -554,9 +555,8 @@ func createBlockChain(start, end uint64) []*common.Block { return block } var blockchain []*common.Block - for seq := uint64(start); seq <= uint64(end); seq++ { + for seq := start; seq <= end; seq++ { block := newBlock(seq) - block.Data.Data = append(block.Data.Data, make([]byte, 100)) block.Header.DataHash = protoutil.BlockDataHash(block.Data) blockchain = append(blockchain, block) }
protoutil/blockutils.go+44 −0 modified@@ -10,6 +10,8 @@ import ( "bytes" "crypto/sha256" "encoding/asn1" + "encoding/base64" + "fmt" "math/big" "github.com/golang/protobuf/proto" @@ -218,3 +220,45 @@ 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") + } + + // If we have a single transaction, and the block is a config block, then no need to check + // well formed-ness, because there cannot be another transaction in the original block. + if IsConfigBlock(block) { + return nil + } + + 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 +}
protoutil/blockutils_test.go+128 −0 modified@@ -374,3 +374,131 @@ 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: "config block", + block: &cb.Block{Data: &cb.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic( + &cb.Envelope{ + Payload: protoutil.MarshalOrPanic(&cb.Payload{ + Header: &cb.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{ + Type: int32(cb.HeaderType_CONFIG), + }), + }, + }), + }), + }, + }}, + }, + { + 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: "cannot parse invalid wire-format data", + block: &cb.Block{ + Data: &cb.BlockData{ + Data: [][]byte{ + marshalOrPanic(&cb.Envelope{ + Payload: []byte{1, 2, 3}, + Signature: []byte{4, 5, 6}, + })[9:], + }, + }, + }, + }, + } { + tst := tst + 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) + } + }) + } +}
protoutil/commonutils.go+10 −1 modified@@ -188,7 +188,16 @@ func SignOrPanic(signer identity.Signer, msg []byte) []byte { // IsConfigBlock validates whenever given block contains configuration // update transaction func IsConfigBlock(block *cb.Block) bool { - envelope, err := ExtractEnvelope(block, 0) + if block.Data == nil { + return false + } + + if len(block.Data.Data) != 1 { + return false + } + + marshaledEnvelope := block.Data.Data[0] + envelope, err := GetEnvelopeFromBlock(marshaledEnvelope) if err != nil { return false }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-v9w2-543f-h69mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-46132ghsaADVISORY
- github.com/hyperledger/fabric/commit/389b2e66de9a6fbc6043216d554c97bbbdf0e008ghsaWEB
- github.com/hyperledger/fabric/commit/93bef10bd3ce3c54d7f3b064f765dbde61da7defghsaWEB
- github.com/hyperledger/fabric/pull/4503ghsaWEB
- github.com/hyperledger/fabric/pull/4504ghsaWEB
- github.com/hyperledger/fabric/releases/tag/v2.2.14ghsaWEB
- github.com/hyperledger/fabric/releases/tag/v2.5.5ghsaWEB
- github.com/hyperledger/fabric/security/advisories/GHSA-v9w2-543f-h69mghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.