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 }