From ca02c607d0f9de15ccd804932c86173cf3ac22d8 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Thu, 9 Feb 2017 15:48:14 -0500 Subject: [PATCH] [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 --- core/chaincode/platforms/car/platform.go | 5 ++ core/chaincode/platforms/golang/platform.go | 54 +++++++++++++ .../platforms/golang/platform_test.go | 76 +++++++++++++++++++ core/chaincode/platforms/java/platform.go | 5 ++ core/chaincode/platforms/platforms.go | 1 + protos/utils/proputils.go | 12 +++ 6 files changed, 153 insertions(+) create mode 100644 core/chaincode/platforms/golang/platform_test.go diff --git a/core/chaincode/platforms/car/platform.go b/core/chaincode/platforms/car/platform.go index 857e0c34f73..22da74930ea 100644 --- a/core/chaincode/platforms/car/platform.go +++ b/core/chaincode/platforms/car/platform.go @@ -36,6 +36,11 @@ func (carPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error { return nil } +func (carPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error { + // CAR platform will validate the code package within chaintool + return nil +} + func (carPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) { return ioutil.ReadFile(spec.ChaincodeId.Path) diff --git a/core/chaincode/platforms/golang/platform.go b/core/chaincode/platforms/golang/platform.go index 07d375e6df5..5cfa11a18fb 100644 --- a/core/chaincode/platforms/golang/platform.go +++ b/core/chaincode/platforms/golang/platform.go @@ -27,6 +27,8 @@ import ( "path/filepath" "strings" + "regexp" + cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos/peer" ) @@ -94,6 +96,57 @@ func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error { return nil } +func (goPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error { + + // FAB-2122: Scan the provided tarball to ensure it only contains source-code under + // /src/$packagename. We do not want to allow something like ./pkg/shady.a to be installed under + // $GOPATH within the container. Note, we do not look deeper than the path at this time + // with the knowledge that only the go/cgo compiler will execute for now. We will remove the source + // from the system after the compilation as an extra layer of protection. + // + // It should be noted that we cannot catch every threat with these techniques. Therefore, + // the container itself needs to be the last line of defense and be configured to be + // resilient in enforcing constraints. However, we should still do our best to keep as much + // garbage out of the system as possible. + re := regexp.MustCompile(`(/)?src/.*`) + is := bytes.NewReader(cds.CodePackage) + gr, err := gzip.NewReader(is) + if err != nil { + return fmt.Errorf("failure opening codepackage gzip stream: %s", err) + } + tr := tar.NewReader(gr) + + for { + header, err := tr.Next() + if err != nil { + // We only get here if there are no more entries to scan + break + } + + // -------------------------------------------------------------------------------------- + // Check name for conforming path + // -------------------------------------------------------------------------------------- + if !re.MatchString(header.Name) { + return fmt.Errorf("Illegal file detected in payload: \"%s\"", header.Name) + } + + // -------------------------------------------------------------------------------------- + // Check that file mode makes sense + // -------------------------------------------------------------------------------------- + // Acceptable flags: + // ISREG == 0100000 + // -rw-rw-rw- == 0666 + // + // Anything else is suspect in this context and will be rejected + // -------------------------------------------------------------------------------------- + if header.Mode&^0100666 != 0 { + return fmt.Errorf("Illegal file mode detected for file %s: %o", header.Name, header.Mode) + } + } + + return nil +} + // WritePackage writes the Go chaincode package func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) { @@ -141,6 +194,7 @@ func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec) buf = append(buf, "ADD codepackage.tgz /tmp/codepackage") //let the executable's name be chaincode ID's name buf = append(buf, fmt.Sprintf("RUN GOPATH=/tmp/codepackage:$GOPATH go build -o /usr/local/bin/chaincode %s", urlLocation)) + buf = append(buf, "RUN rm -rf /tmp/codepackage") // FAB-2122: scrub source after it is no longer needed dockerFileContents := strings.Join(buf, "\n") diff --git a/core/chaincode/platforms/golang/platform_test.go b/core/chaincode/platforms/golang/platform_test.go new file mode 100644 index 00000000000..05aa5fe7fdc --- /dev/null +++ b/core/chaincode/platforms/golang/platform_test.go @@ -0,0 +1,76 @@ +package golang + +import ( + "testing" + + "archive/tar" + "bytes" + "compress/gzip" + "time" + + pb "github.com/hyperledger/fabric/protos/peer" +) + +func writeBytesToPackage(name string, payload []byte, mode int64, tw *tar.Writer) error { + //Make headers identical by using zero time + var zeroTime time.Time + tw.WriteHeader(&tar.Header{Name: name, Size: int64(len(payload)), ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime, Mode: mode}) + tw.Write(payload) + + return nil +} + +func generateFakeCDS(path, file string, mode int64) (*pb.ChaincodeDeploymentSpec, error) { + codePackage := bytes.NewBuffer(nil) + gw := gzip.NewWriter(codePackage) + tw := tar.NewWriter(gw) + + payload := make([]byte, 25, 25) + err := writeBytesToPackage(file, payload, mode, tw) + if err != nil { + return nil, err + } + + tw.Close() + gw.Close() + + cds := &pb.ChaincodeDeploymentSpec{ + ChaincodeSpec: &pb.ChaincodeSpec{ + ChaincodeId: &pb.ChaincodeID{ + Name: "Bad Code", + Path: path, + }, + }, + CodePackage: codePackage.Bytes(), + } + + return cds, nil +} + +type spec struct { + Path, File string + Mode int64 + SuccessExpected bool +} + +func TestValidateCDS(t *testing.T) { + platform := &Platform{} + + specs := make([]spec, 0) + specs = append(specs, spec{Path: "path/to/nowhere", File: "/bin/warez", Mode: 0100400, SuccessExpected: false}) + specs = append(specs, spec{Path: "path/to/somewhere", File: "/src/path/to/somewhere/main.go", Mode: 0100400, SuccessExpected: true}) + specs = append(specs, spec{Path: "path/to/somewhere", File: "/src/path/to/somewhere/warez", Mode: 0100555, SuccessExpected: false}) + + for _, s := range specs { + cds, err := generateFakeCDS(s.Path, s.File, s.Mode) + + err = platform.ValidateDeploymentSpec(cds) + if s.SuccessExpected == true && err != nil { + t.Errorf("Unexpected failure: %s", err) + } + if s.SuccessExpected == false && err == nil { + t.Log("Expected validation failure") + t.Fail() + } + } +} diff --git a/core/chaincode/platforms/java/platform.go b/core/chaincode/platforms/java/platform.go index 7ce5ec94d8d..d3149348967 100644 --- a/core/chaincode/platforms/java/platform.go +++ b/core/chaincode/platforms/java/platform.go @@ -87,6 +87,11 @@ func (javaPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error { return nil } +func (javaPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error { + // FIXME: Java platform needs to implement its own validation similar to GOLANG + return nil +} + // WritePackage writes the java chaincode package func (javaPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) { diff --git a/core/chaincode/platforms/platforms.go b/core/chaincode/platforms/platforms.go index bf2bc21ab91..272d02927b1 100644 --- a/core/chaincode/platforms/platforms.go +++ b/core/chaincode/platforms/platforms.go @@ -40,6 +40,7 @@ import ( // the given platform type Platform interface { ValidateSpec(spec *pb.ChaincodeSpec) error + ValidateDeploymentSpec(spec *pb.ChaincodeDeploymentSpec) error GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) GenerateDockerfile(spec *pb.ChaincodeDeploymentSpec) (string, error) GenerateDockerBuild(spec *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error diff --git a/protos/utils/proputils.go b/protos/utils/proputils.go index 71f8369686d..593f07f81e5 100644 --- a/protos/utils/proputils.go +++ b/protos/utils/proputils.go @@ -22,6 +22,7 @@ import ( "errors" "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode/platforms" "github.com/hyperledger/fabric/core/crypto/primitives" "github.com/hyperledger/fabric/protos/common" "github.com/hyperledger/fabric/protos/peer" @@ -116,6 +117,17 @@ func GetChaincodeDeploymentSpec(code []byte) (*peer.ChaincodeDeploymentSpec, err return nil, err } + // FAB-2122: Validate the CDS according to platform specific requirements + platform, err := platforms.Find(cds.ChaincodeSpec.Type) + if err != nil { + return nil, err + } + + err = platform.ValidateDeploymentSpec(cds) + if err != nil { + return nil, err + } + return cds, nil }