Skip to content

Commit

Permalink
Merge "[FAB-2122] Scan codepackage for illegal content"
Browse files Browse the repository at this point in the history
  • Loading branch information
ghaskins authored and Gerrit Code Review committed Feb 17, 2017
2 parents abcc019 + ca02c60 commit 22795e3
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 0 deletions.
5 changes: 5 additions & 0 deletions core/chaincode/platforms/car/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions core/chaincode/platforms/golang/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"path/filepath"
"strings"

"regexp"

cutil "github.com/hyperledger/fabric/core/container/util"
pb "github.com/hyperledger/fabric/protos/peer"
)
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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")

Expand Down
76 changes: 76 additions & 0 deletions core/chaincode/platforms/golang/platform_test.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
5 changes: 5 additions & 0 deletions core/chaincode/platforms/java/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down
1 change: 1 addition & 0 deletions core/chaincode/platforms/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions protos/utils/proputils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 22795e3

Please sign in to comment.