Skip to content

Commit 5cf3596

Browse files
committed
[FAB-12287] handle successful decode of bad data
LSCC handles CDSPackage and SignedCDSPackage instances in pretty much the same way. The current pattern seems to be to try to decode data from lifecycle into CDSPackage and, if it works, use it; if it doesn't try to decode it into a SignedCDSPackage. That would be great if protobuf were guaranteed to fail decoding the wrong object but it doesn't. In preparation for an upgrade to the proto package, the CDSPackage validation needs to be extended to catch more scenarios where bad data has been "successfully" unmarshaled into an object. This needs to be done in a way that increases confidence in the decoding process without duplicating validation that lives in lifecycle. Change-Id: Ib53ddf1260a08e2f2178ce0f8862da5429aff87b Signed-off-by: Matthew Sykes <sykesmat@us.ibm.com>
1 parent 51ec113 commit 5cf3596

File tree

6 files changed

+125
-17
lines changed

6 files changed

+125
-17
lines changed

core/common/ccprovider/ccprovider.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os"
1414
"path/filepath"
1515
"strings"
16+
"unicode"
1617

1718
"github.com/golang/protobuf/proto"
1819
"github.com/hyperledger/fabric/common/chaincode"
@@ -83,6 +84,16 @@ func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) {
8384
return GetChaincodePackageFromPath(ccname, ccversion, chaincodeInstallPath)
8485
}
8586

87+
// isPrintable is used by CDSPackage and SignedCDSPackage validation to
88+
// detect garbage strings in unmarshaled proto fields where printable
89+
// characters are expected.
90+
func isPrintable(name string) bool {
91+
notASCII := func(r rune) bool {
92+
return !unicode.IsPrint(r)
93+
}
94+
return strings.IndexFunc(name, notASCII) == -1
95+
}
96+
8697
// GetChaincodePackage returns the chaincode package from the file system
8798
func GetChaincodePackageFromPath(ccname string, ccversion string, ccInstallPath string) ([]byte, error) {
8899
path := fmt.Sprintf("%s/%s.%s", ccInstallPath, ccname, ccversion)
@@ -306,7 +317,7 @@ func GetCCPackage(buf []byte) (CCPackage, error) {
306317
return scds, nil
307318
}
308319

309-
return nil, errors.New("could not unmarshaled chaincode package to CDS or SignedCDS")
320+
return nil, errors.New("could not unmarshal chaincode package to CDS or SignedCDS")
310321
}
311322

312323
// GetInstalledChaincodes returns a map whose key is the chaincode id and

core/common/ccprovider/ccprovider_test.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ SPDX-License-Identifier: Apache-2.0
77
package ccprovider_test
88

99
import (
10-
"fmt"
10+
"crypto/sha256"
1111
"io/ioutil"
12-
"math/rand"
1312
"os"
1413
"path"
14+
"strings"
1515
"testing"
16-
"time"
1716

1817
"github.com/golang/protobuf/proto"
1918
"github.com/hyperledger/fabric/common/chaincode"
@@ -24,7 +23,7 @@ import (
2423
)
2524

2625
func TestInstalledCCs(t *testing.T) {
27-
tmpDir := setupDirectoryStructure(t)
26+
tmpDir, hashes := setupDirectoryStructure(t)
2827
defer func() {
2928
os.RemoveAll(tmpDir)
3029
}()
@@ -44,12 +43,12 @@ func TestInstalledCCs(t *testing.T) {
4443
{
4544
Name: "example02",
4645
Version: "1.0",
47-
Id: []byte{45, 186, 93, 188, 51, 158, 115, 22, 174, 162, 104, 63, 175, 131, 156, 27, 123, 30, 226, 49, 61, 183, 146, 17, 37, 136, 17, 141, 240, 102, 170, 53},
46+
Id: hashes["example02.1.0"],
4847
},
4948
{
5049
Name: "example04",
5150
Version: "1",
52-
Id: []byte{45, 186, 93, 188, 51, 158, 115, 22, 174, 162, 104, 63, 175, 131, 156, 27, 123, 30, 226, 49, 61, 183, 146, 17, 37, 136, 17, 141, 240, 102, 170, 53},
51+
Id: hashes["example04.1"],
5352
},
5453
},
5554
directory: "nonempty",
@@ -106,15 +105,15 @@ func TestInstalledCCs(t *testing.T) {
106105
}
107106
}
108107

109-
func setupDirectoryStructure(t *testing.T) string {
108+
func setupDirectoryStructure(t *testing.T) (string, map[string][]byte) {
110109
files := []string{
111110
"example02.1.0", // Version contains the delimiter '.' is a valid case
112111
"example03", // No version specified
113112
"example04.1", // Version doesn't contain the '.' delimiter
114113
}
115-
rand.Seed(time.Now().UnixNano())
116-
tmp := path.Join(os.TempDir(), fmt.Sprintf("%d", rand.Int()))
117-
assert.NoError(t, os.Mkdir(tmp, 0755))
114+
hashes := map[string][]byte{}
115+
tmp, err := ioutil.TempDir("", "test-installed-cc")
116+
assert.NoError(t, err)
118117
dir := path.Join(tmp, "empty")
119118
assert.NoError(t, os.Mkdir(dir, 0755))
120119
dir = path.Join(tmp, "nonempty")
@@ -124,22 +123,42 @@ func setupDirectoryStructure(t *testing.T) string {
124123
dir = path.Join(tmp, "nopermissionforfiles")
125124
assert.NoError(t, os.Mkdir(dir, 0755))
126125
noPermissionFile := path.Join(tmp, "nopermissionforfiles", "nopermission.1")
127-
_, err := os.Create(noPermissionFile)
126+
_, err = os.Create(noPermissionFile)
128127
assert.NoError(t, err)
129128
dir = path.Join(tmp, "nonempty")
130129
assert.NoError(t, os.Mkdir(path.Join(tmp, "nonempty", "directory"), 0755))
131130
for _, f := range files {
131+
var name, ver string
132+
parts := strings.SplitN(f, ".", 2)
133+
name = parts[0]
134+
if len(parts) > 1 {
135+
ver = parts[1]
136+
}
132137
file, err := os.Create(path.Join(dir, f))
133138
assert.NoError(t, err)
134139
cds := &peer.ChaincodeDeploymentSpec{
135140
ChaincodeSpec: &peer.ChaincodeSpec{
136-
ChaincodeId: &peer.ChaincodeID{},
141+
ChaincodeId: &peer.ChaincodeID{Name: name, Version: ver},
137142
},
138143
}
144+
145+
codehash := sha256.New()
146+
codehash.Write(cds.CodePackage)
147+
148+
metahash := sha256.New()
149+
metahash.Write([]byte(name))
150+
metahash.Write([]byte(ver))
151+
152+
hash := sha256.New()
153+
hash.Write(codehash.Sum(nil))
154+
hash.Write(metahash.Sum(nil))
155+
156+
hashes[f] = hash.Sum(nil)
157+
139158
b, _ := proto.Marshal(cds)
140159
file.Write(b)
141160
file.Close()
142161
}
143162

144-
return tmp
163+
return tmp, hashes
145164
}

core/common/ccprovider/cdspackage.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,16 @@ func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error {
184184
return fmt.Errorf("nil data")
185185
}
186186

187+
// This is a hack. LSCC expects a specific LSCC error when names are invalid so it
188+
// has its own validation code. We can't use that error because of import cycles.
189+
// Unfortunately, we also need to check if what have makes some sort of sense as
190+
// protobuf will gladly deserialize garbage and there are paths where we assume that
191+
// a successful unmarshal means everything works but, if it fails, we try to unmarshal
192+
// into something different.
193+
if !isPrintable(ccdata.Name) {
194+
return fmt.Errorf("invalid chaincode name: %q", ccdata.Name)
195+
}
196+
187197
if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version {
188198
return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId)
189199
}
@@ -236,7 +246,12 @@ func (ccpack *CDSPackage) InitFromPath(ccname string, ccversion string, path str
236246
return nil, nil, err
237247
}
238248

239-
if _, err = ccpack.InitFromBuffer(buf); err != nil {
249+
ccdata, err := ccpack.InitFromBuffer(buf)
250+
if err != nil {
251+
return nil, nil, err
252+
}
253+
254+
if err := ccpack.ValidateCC(ccdata); err != nil {
240255
return nil, nil, err
241256
}
242257

core/common/ccprovider/cdspackage_test.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,57 @@ func TestValidateCCErrorPaths(t *testing.T) {
212212
assert.Error(t, err)
213213
assert.Contains(t, err.Error(), "uninitialized package", "Unexpected error returned")
214214

215-
cpack.depSpec = &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: 1, ChaincodeId: &pb.ChaincodeID{Name: "testcc", Version: "0"},
216-
Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("")}}}, CodePackage: []byte("code")}
215+
cpack.depSpec = &pb.ChaincodeDeploymentSpec{
216+
CodePackage: []byte("code"),
217+
ChaincodeSpec: &pb.ChaincodeSpec{
218+
Type: 1,
219+
ChaincodeId: &pb.ChaincodeID{Name: "testcc", Version: "0"},
220+
Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("")}},
221+
},
222+
}
217223
err = cpack.ValidateCC(ccdata)
218224
assert.Error(t, err)
219225
assert.Contains(t, err.Error(), "nil data", "Unexpected error returned")
226+
227+
// invalid encoded name
228+
cpack = &CDSPackage{}
229+
ccdata = &ChaincodeData{Name: "\027"}
230+
cpack.depSpec = &pb.ChaincodeDeploymentSpec{
231+
CodePackage: []byte("code"),
232+
ChaincodeSpec: &pb.ChaincodeSpec{
233+
ChaincodeId: &pb.ChaincodeID{Name: ccdata.Name, Version: "0"},
234+
},
235+
}
236+
cpack.data = &CDSData{}
237+
err = cpack.ValidateCC(ccdata)
238+
assert.Error(t, err)
239+
assert.Contains(t, err.Error(), `invalid chaincode name: "\x17"`)
240+
241+
// mismatched names
242+
cpack = &CDSPackage{}
243+
ccdata = &ChaincodeData{Name: "Tom"}
244+
cpack.depSpec = &pb.ChaincodeDeploymentSpec{
245+
CodePackage: []byte("code"),
246+
ChaincodeSpec: &pb.ChaincodeSpec{
247+
ChaincodeId: &pb.ChaincodeID{Name: "Jerry", Version: "0"},
248+
},
249+
}
250+
cpack.data = &CDSData{}
251+
err = cpack.ValidateCC(ccdata)
252+
assert.Error(t, err)
253+
assert.Contains(t, err.Error(), `invalid chaincode data name:"Tom" (name:"Jerry" version:"0" )`)
254+
255+
// mismatched versions
256+
cpack = &CDSPackage{}
257+
ccdata = &ChaincodeData{Name: "Tom", Version: "1"}
258+
cpack.depSpec = &pb.ChaincodeDeploymentSpec{
259+
CodePackage: []byte("code"),
260+
ChaincodeSpec: &pb.ChaincodeSpec{
261+
ChaincodeId: &pb.ChaincodeID{Name: ccdata.Name, Version: "0"},
262+
},
263+
}
264+
cpack.data = &CDSData{}
265+
err = cpack.ValidateCC(ccdata)
266+
assert.Error(t, err)
267+
assert.Contains(t, err.Error(), `invalid chaincode data name:"Tom" version:"1" (name:"Tom" version:"0" )`)
220268
}

core/common/ccprovider/sigcdspackage.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) error {
229229
return fmt.Errorf("chaincode deployment spec cannot be nil in a package")
230230
}
231231

232+
// This is a hack. LSCC expects a specific LSCC error when names are invalid so it
233+
// has its own validation code. We can't use that error because of import cycles.
234+
// Unfortunately, we also need to check if what have makes some sort of sense as
235+
// protobuf will gladly deserialize garbage and there are paths where we assume that
236+
// a successful unmarshal means everything works but, if it fails, we try to unmarshal
237+
// into something different.
238+
if !isPrintable(ccdata.Name) {
239+
return fmt.Errorf("invalid chaincode name: %q", ccdata.Name)
240+
}
241+
232242
if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version {
233243
return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId)
234244
}

core/common/ccprovider/sigcdspackage_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ func TestValidateSignedCCErrorPaths(t *testing.T) {
258258
assert.Error(t, err)
259259
assert.Contains(t, err.Error(), "chaincode deployment spec cannot be nil in a package", "Unexpected error validating package")
260260
ccpack.depSpec = depspec
261+
262+
cd = &ChaincodeData{Name: "\027", Version: "0"}
263+
err = ccpack.ValidateCC(cd)
264+
assert.Error(t, err)
265+
assert.Contains(t, err.Error(), `invalid chaincode name: "\x17"`)
261266
}
262267

263268
func TestSigCDSGetCCPackage(t *testing.T) {

0 commit comments

Comments
 (0)