Skip to content

Commit 0631ccd

Browse files
author
Jason Yellick
committed
[FAB-5606] (Backport) Failed ctxu may mutate cache
CR Number 2: Backported for v1.0.2 The configtx code maintains a map of the current config, as derived from the Config proto structure. This map stores references to a cached Config proto structure which is used when constructing the next Config structure. The problem arises when this map is used to construct a new Config to be applied, that it mutates the cached version of of the Config. This is generally fine, so long as the new Config applies successfully, but in the event of bad inputs, such as a bad certificate, the config update fails to apply and is rolled back, but the cache has been mutated and will not be rolled back with it. The observed issue occurs because this Config cache is also used in creating the new channel config template. So, because there is a bad certificate in the config cache, the new channel template attempts to bootstrap using the bad key material, detects the error, and aborts. As noted in the issue, restarting the orderer rebuilds this cache, and channel creation can occur normally once more. This CR fixes the code which constructs a new Config from the config map to create a copy of the cached config in-process, rather than taint the cache with potentially invalid data. Note, there may be novel ways to corrupt this cache which could cause other undesirable behavior. However, prior to the operation which mutates the cache, the config update has been validated to adheer to the security constraints of the channel (including all necessary admin signatures), so it requires in a sense, a conspiracy of channel administrators attempting to corrupt their own channel, so the security implications are limited or non-existant. Change-Id: I56bf6c8bc204785ef6634fd0352466ad3ab6d2af Signed-off-by: Jason Yellick <[email protected]>
1 parent 2cab745 commit 0631ccd

File tree

4 files changed

+28
-12
lines changed

4 files changed

+28
-12
lines changed

common/configtx/configmap.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"strings"
2222

2323
cb "github.com/hyperledger/fabric/protos/common"
24+
25+
"github.com/golang/protobuf/proto"
2426
)
2527

2628
const (
@@ -114,7 +116,7 @@ func configMapToConfig(configMap map[string]comparable) (*cb.ConfigGroup, error)
114116
}
115117

116118
// recurseConfigMap is used only internally by configMapToConfig
117-
// Note, this function mutates the cb.Config* entrieswithin configMap, so errors are generally fatal
119+
// Note, this function no longer mutates the cb.Config* entries within configMap
118120
func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigGroup, error) {
119121
groupPath := GroupPrefix + path
120122
group, ok := configMap[groupPath]
@@ -126,12 +128,15 @@ func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigG
126128
return nil, fmt.Errorf("ConfigGroup not found at group path: %s", groupPath)
127129
}
128130

131+
newConfigGroup := cb.NewConfigGroup()
132+
proto.Merge(newConfigGroup, group.ConfigGroup)
133+
129134
for key, _ := range group.Groups {
130135
updatedGroup, err := recurseConfigMap(path+PathSeparator+key, configMap)
131136
if err != nil {
132137
return nil, err
133138
}
134-
group.Groups[key] = updatedGroup
139+
newConfigGroup.Groups[key] = updatedGroup
135140
}
136141

137142
for key, _ := range group.Values {
@@ -143,7 +148,7 @@ func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigG
143148
if value.ConfigValue == nil {
144149
return nil, fmt.Errorf("ConfigValue not found at value path: %s", valuePath)
145150
}
146-
group.Values[key] = value.ConfigValue
151+
newConfigGroup.Values[key] = proto.Clone(value.ConfigValue).(*cb.ConfigValue)
147152
}
148153

149154
for key, _ := range group.Policies {
@@ -155,8 +160,9 @@ func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigG
155160
if policy.ConfigPolicy == nil {
156161
return nil, fmt.Errorf("ConfigPolicy not found at policy path: %s", policyPath)
157162
}
158-
group.Policies[key] = policy.ConfigPolicy
163+
newConfigGroup.Policies[key] = proto.Clone(policy.ConfigPolicy).(*cb.ConfigPolicy)
164+
logger.Debugf("Setting policy for key %s to %+v", key, group.Policies[key])
159165
}
160166

161-
return group.ConfigGroup, nil
167+
return newConfigGroup, nil
162168
}

common/configtx/configmap_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ package configtx
1919
import (
2020
"testing"
2121

22-
"github.com/stretchr/testify/assert"
23-
2422
cb "github.com/hyperledger/fabric/protos/common"
23+
24+
logging "github.com/op/go-logging"
25+
"github.com/stretchr/testify/assert"
2526
)
2627

28+
func init() {
29+
logging.SetLevel(logging.DEBUG, "")
30+
}
31+
2732
func TestBadKey(t *testing.T) {
2833
assert.Error(t, addToMap(comparable{key: "[Label]", path: []string{}}, make(map[string]comparable)),
2934
"Should have errored on key with illegal characters")
@@ -90,4 +95,7 @@ func TestMapConfigBack(t *testing.T) {
9095
assert.NoError(t, err, "Should not have errored building config")
9196

9297
assert.Equal(t, config, newConfig, "Should have transformed config map back from confMap")
98+
99+
newConfig.Values["Value"] = &cb.ConfigValue{}
100+
assert.NotEqual(t, config, newConfig, "Mutating the new config should not mutate the existing config")
93101
}

common/configtx/manager.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ package configtx
1818

1919
import (
2020
"fmt"
21-
"reflect"
2221
"regexp"
2322

2423
"github.com/hyperledger/fabric/common/configtx/api"
2524
"github.com/hyperledger/fabric/common/flogging"
2625
cb "github.com/hyperledger/fabric/protos/common"
2726
"github.com/hyperledger/fabric/protos/utils"
27+
28+
"github.com/golang/protobuf/proto"
2829
)
2930

3031
var logger = flogging.MustGetLogger("common/configtx")
@@ -232,7 +233,8 @@ func (cm *configManager) prepareApply(configEnv *cb.ConfigEnvelope) (*configResu
232233
return nil, fmt.Errorf("Could not turn configMap back to channelGroup: %s", err)
233234
}
234235

235-
if !reflect.DeepEqual(channelGroup, configEnv.Config.ChannelGroup) {
236+
// reflect.Equal will not work here, because it considers nil and empty maps as different
237+
if !proto.Equal(channelGroup, configEnv.Config.ChannelGroup) {
236238
return nil, fmt.Errorf("ConfigEnvelope LastUpdate did not produce the supplied config result")
237239
}
238240

orderer/multichain/systemchain.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package multichain
1818

1919
import (
2020
"fmt"
21-
"reflect"
2221

2322
"github.com/hyperledger/fabric/common/config"
2423
"github.com/hyperledger/fabric/common/configtx"
@@ -140,8 +139,9 @@ func (scf *systemChainFilter) authorize(configEnvelope *cb.ConfigEnvelope) (conf
140139
func (scf *systemChainFilter) inspect(proposedManager, configManager configtxapi.Manager) error {
141140
proposedEnv := proposedManager.ConfigEnvelope()
142141
actualEnv := configManager.ConfigEnvelope()
143-
if !reflect.DeepEqual(proposedEnv.Config, actualEnv.Config) {
144-
return fmt.Errorf("The config proposed by the channel creation request did not match the config received with the channel creation request")
142+
// reflect.DeepEqual will not work here, because it considers nil and empty maps as unequal
143+
if !proto.Equal(proposedEnv.Config, actualEnv.Config) {
144+
return fmt.Errorf("config proposed by the channel creation request did not match the config received with the channel creation request")
145145
}
146146
return nil
147147
}

0 commit comments

Comments
 (0)