Skip to content

Commit ca02c60

Browse files
committed
[FAB-2122] Scan codepackage for illegal content
We allow each chaincode platform to define its own deployment validation logic, and then implement a FAB-2122 oriented filter for GOLANG. What this means in practice is we perform some basic checks against the tarball that was passed in via the DeploymentSpec. For instance, there is no reason for a client to submit binaries (+x set), files in /bin/*, or source packages outside of the declared package. As noted, this doesn't provide 100% protection but it does at least reject some basic things that are clearly garbage. Change-Id: I6c725d4cb0522a8be5717ab80d4f6205e83bf858 Signed-off-by: Greg Haskins <[email protected]>
1 parent 0a0ba86 commit ca02c60

File tree

6 files changed

+153
-0
lines changed

6 files changed

+153
-0
lines changed

core/chaincode/platforms/car/platform.go

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func (carPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error {
3636
return nil
3737
}
3838

39+
func (carPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error {
40+
// CAR platform will validate the code package within chaintool
41+
return nil
42+
}
43+
3944
func (carPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
4045

4146
return ioutil.ReadFile(spec.ChaincodeId.Path)

core/chaincode/platforms/golang/platform.go

+54
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
"path/filepath"
2828
"strings"
2929

30+
"regexp"
31+
3032
cutil "github.com/hyperledger/fabric/core/container/util"
3133
pb "github.com/hyperledger/fabric/protos/peer"
3234
)
@@ -94,6 +96,57 @@ func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error {
9496
return nil
9597
}
9698

99+
func (goPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error {
100+
101+
// FAB-2122: Scan the provided tarball to ensure it only contains source-code under
102+
// /src/$packagename. We do not want to allow something like ./pkg/shady.a to be installed under
103+
// $GOPATH within the container. Note, we do not look deeper than the path at this time
104+
// with the knowledge that only the go/cgo compiler will execute for now. We will remove the source
105+
// from the system after the compilation as an extra layer of protection.
106+
//
107+
// It should be noted that we cannot catch every threat with these techniques. Therefore,
108+
// the container itself needs to be the last line of defense and be configured to be
109+
// resilient in enforcing constraints. However, we should still do our best to keep as much
110+
// garbage out of the system as possible.
111+
re := regexp.MustCompile(`(/)?src/.*`)
112+
is := bytes.NewReader(cds.CodePackage)
113+
gr, err := gzip.NewReader(is)
114+
if err != nil {
115+
return fmt.Errorf("failure opening codepackage gzip stream: %s", err)
116+
}
117+
tr := tar.NewReader(gr)
118+
119+
for {
120+
header, err := tr.Next()
121+
if err != nil {
122+
// We only get here if there are no more entries to scan
123+
break
124+
}
125+
126+
// --------------------------------------------------------------------------------------
127+
// Check name for conforming path
128+
// --------------------------------------------------------------------------------------
129+
if !re.MatchString(header.Name) {
130+
return fmt.Errorf("Illegal file detected in payload: \"%s\"", header.Name)
131+
}
132+
133+
// --------------------------------------------------------------------------------------
134+
// Check that file mode makes sense
135+
// --------------------------------------------------------------------------------------
136+
// Acceptable flags:
137+
// ISREG == 0100000
138+
// -rw-rw-rw- == 0666
139+
//
140+
// Anything else is suspect in this context and will be rejected
141+
// --------------------------------------------------------------------------------------
142+
if header.Mode&^0100666 != 0 {
143+
return fmt.Errorf("Illegal file mode detected for file %s: %o", header.Name, header.Mode)
144+
}
145+
}
146+
147+
return nil
148+
}
149+
97150
// WritePackage writes the Go chaincode package
98151
func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
99152

@@ -141,6 +194,7 @@ func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec)
141194
buf = append(buf, "ADD codepackage.tgz /tmp/codepackage")
142195
//let the executable's name be chaincode ID's name
143196
buf = append(buf, fmt.Sprintf("RUN GOPATH=/tmp/codepackage:$GOPATH go build -o /usr/local/bin/chaincode %s", urlLocation))
197+
buf = append(buf, "RUN rm -rf /tmp/codepackage") // FAB-2122: scrub source after it is no longer needed
144198

145199
dockerFileContents := strings.Join(buf, "\n")
146200

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package golang
2+
3+
import (
4+
"testing"
5+
6+
"archive/tar"
7+
"bytes"
8+
"compress/gzip"
9+
"time"
10+
11+
pb "github.com/hyperledger/fabric/protos/peer"
12+
)
13+
14+
func writeBytesToPackage(name string, payload []byte, mode int64, tw *tar.Writer) error {
15+
//Make headers identical by using zero time
16+
var zeroTime time.Time
17+
tw.WriteHeader(&tar.Header{Name: name, Size: int64(len(payload)), ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime, Mode: mode})
18+
tw.Write(payload)
19+
20+
return nil
21+
}
22+
23+
func generateFakeCDS(path, file string, mode int64) (*pb.ChaincodeDeploymentSpec, error) {
24+
codePackage := bytes.NewBuffer(nil)
25+
gw := gzip.NewWriter(codePackage)
26+
tw := tar.NewWriter(gw)
27+
28+
payload := make([]byte, 25, 25)
29+
err := writeBytesToPackage(file, payload, mode, tw)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
tw.Close()
35+
gw.Close()
36+
37+
cds := &pb.ChaincodeDeploymentSpec{
38+
ChaincodeSpec: &pb.ChaincodeSpec{
39+
ChaincodeId: &pb.ChaincodeID{
40+
Name: "Bad Code",
41+
Path: path,
42+
},
43+
},
44+
CodePackage: codePackage.Bytes(),
45+
}
46+
47+
return cds, nil
48+
}
49+
50+
type spec struct {
51+
Path, File string
52+
Mode int64
53+
SuccessExpected bool
54+
}
55+
56+
func TestValidateCDS(t *testing.T) {
57+
platform := &Platform{}
58+
59+
specs := make([]spec, 0)
60+
specs = append(specs, spec{Path: "path/to/nowhere", File: "/bin/warez", Mode: 0100400, SuccessExpected: false})
61+
specs = append(specs, spec{Path: "path/to/somewhere", File: "/src/path/to/somewhere/main.go", Mode: 0100400, SuccessExpected: true})
62+
specs = append(specs, spec{Path: "path/to/somewhere", File: "/src/path/to/somewhere/warez", Mode: 0100555, SuccessExpected: false})
63+
64+
for _, s := range specs {
65+
cds, err := generateFakeCDS(s.Path, s.File, s.Mode)
66+
67+
err = platform.ValidateDeploymentSpec(cds)
68+
if s.SuccessExpected == true && err != nil {
69+
t.Errorf("Unexpected failure: %s", err)
70+
}
71+
if s.SuccessExpected == false && err == nil {
72+
t.Log("Expected validation failure")
73+
t.Fail()
74+
}
75+
}
76+
}

core/chaincode/platforms/java/platform.go

+5
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ func (javaPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error {
8787
return nil
8888
}
8989

90+
func (javaPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error {
91+
// FIXME: Java platform needs to implement its own validation similar to GOLANG
92+
return nil
93+
}
94+
9095
// WritePackage writes the java chaincode package
9196
func (javaPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
9297

core/chaincode/platforms/platforms.go

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
// the given platform
4141
type Platform interface {
4242
ValidateSpec(spec *pb.ChaincodeSpec) error
43+
ValidateDeploymentSpec(spec *pb.ChaincodeDeploymentSpec) error
4344
GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error)
4445
GenerateDockerfile(spec *pb.ChaincodeDeploymentSpec) (string, error)
4546
GenerateDockerBuild(spec *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error

protos/utils/proputils.go

+12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323

2424
"github.com/golang/protobuf/proto"
25+
"github.com/hyperledger/fabric/core/chaincode/platforms"
2526
"github.com/hyperledger/fabric/core/crypto/primitives"
2627
"github.com/hyperledger/fabric/protos/common"
2728
"github.com/hyperledger/fabric/protos/peer"
@@ -116,6 +117,17 @@ func GetChaincodeDeploymentSpec(code []byte) (*peer.ChaincodeDeploymentSpec, err
116117
return nil, err
117118
}
118119

120+
// FAB-2122: Validate the CDS according to platform specific requirements
121+
platform, err := platforms.Find(cds.ChaincodeSpec.Type)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
err = platform.ValidateDeploymentSpec(cds)
127+
if err != nil {
128+
return nil, err
129+
}
130+
119131
return cds, nil
120132
}
121133

0 commit comments

Comments
 (0)