Skip to content

Commit

Permalink
[FAB-2122] Scan codepackage for illegal content
Browse files Browse the repository at this point in the history
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 <gregory.haskins@gmail.com>
  • Loading branch information
ghaskins committed Feb 15, 2017
1 parent 0a0ba86 commit ca02c60
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 ca02c60

Please sign in to comment.