diff --git a/core/chaincode/exectransaction_test.go b/core/chaincode/exectransaction_test.go index 9fe7984520f..0d0f2274b0e 100644 --- a/core/chaincode/exectransaction_test.go +++ b/core/chaincode/exectransaction_test.go @@ -1475,6 +1475,60 @@ func TestChaincodeQueryChaincodeUsingInvoke(t *testing.T) { theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) } +// test that invoking a security-sensitive system chaincode fails +func TestChaincodeInvokesForbiddenSystemChaincode(t *testing.T) { + chainID := util.GetTestChainID() + + lis, err := initPeer(chainID) + if err != nil { + t.Fail() + t.Logf("Error creating peer: %s", err) + } + + defer finitPeer(lis, chainID) + + var ctxt = context.Background() + + var nextBlockNumber uint64 + + // Deploy second chaincode + url := "github.com/hyperledger/fabric/examples/chaincode/go/passthru" + + cID := &pb.ChaincodeID{Name: "pthru", Path: url, Version: "0"} + f := "init" + args := util.ToChaincodeArgs(f) + + spec := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + + cccid := ccprovider.NewCCContext(chainID, "pthru", "0", "", false, nil, nil) + + _, err = deploy(ctxt, cccid, spec, nextBlockNumber) + nextBlockNumber++ + ccID := spec.ChaincodeId.Name + if err != nil { + t.Fail() + t.Logf("Error initializing chaincode %s(%s)", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + time.Sleep(time.Second) + + // send an invoke to pass thru to invoke "escc" system chaincode + // this should fail + args = util.ToChaincodeArgs("escc/"+chainID, "getid", chainID, "pthru") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + // Invoke chaincode + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber) + if err == nil { + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + t.Logf("invoking <%s> should have failed", ccID) + t.Fail() + return + } +} + // Test the execution of a chaincode that invokes system chaincode // uses the "pthru" chaincode to query "lscc" for the "pthru" chaincode func TestChaincodeInvokesSystemChaincode(t *testing.T) { diff --git a/core/chaincode/handler.go b/core/chaincode/handler.go index 3ed8998f52b..6c72a4cdf54 100644 --- a/core/chaincode/handler.go +++ b/core/chaincode/handler.go @@ -29,7 +29,7 @@ import ( "github.com/hyperledger/fabric/common/util" "github.com/hyperledger/fabric/core/common/ccprovider" "github.com/hyperledger/fabric/core/common/sysccprovider" - ccintf "github.com/hyperledger/fabric/core/container/ccintf" + "github.com/hyperledger/fabric/core/container/ccintf" "github.com/hyperledger/fabric/core/ledger" "github.com/hyperledger/fabric/core/ledger/ledgerconfig" "github.com/hyperledger/fabric/core/peer" @@ -238,12 +238,20 @@ func (handler *Handler) deleteQueryIterator(txContext *transactionContext, txid } // Check if the transactor is allow to call this chaincode on this channel -func (handler *Handler) checkACL(signedProp *pb.SignedProposal, proposal *pb.Proposal, calledCC *ccParts) *pb.ChaincodeMessage { - // TODO: Decide what to pass in to verify that this transactor can access this - // channel (chID) and chaincode (ccID). Very likely we need the signedProposal - // which contains the sig and creator cert +func (handler *Handler) checkACL(signedProp *pb.SignedProposal, proposal *pb.Proposal, calledCC *ccParts) error { + // ensure that we don't invoke a system chaincode + // that is not invokable through a cc2cc invocation + if sysccprovider.GetSystemChaincodeProvider().IsSysCCAndNotInvokableCC2CC(calledCC.name) { + return fmt.Errorf("System chaincode %s cannot be invoked with a cc2cc invocation", calledCC.name) + } + + // if we are here, all we know is that the invoked chaincode is either + // - a system chaincode that *is* invokable through a cc2cc + // (but we may still have to determine whether the invoker + // can perform this invocation) + // - an application chaincode (and we still need to determine + // whether the invoker can invoke it) - // If error, return ChaincodeMessage with type ChaincodeMessage_ERROR return nil } @@ -1269,8 +1277,12 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { shorttxid(msg.Txid), calledCcParts.name, calledCcParts.suffix) } - triggerNextStateMsg = handler.checkACL(txContext.signedProp, txContext.proposal, calledCcParts) - if triggerNextStateMsg != nil { + err := handler.checkACL(txContext.signedProp, txContext.proposal, calledCcParts) + if err != nil { + chaincodeLogger.Errorf("[%s] C-call-C %s on channel %s failed check ACL [%v]: [%s]", + shorttxid(msg.Txid), calledCcParts.name, calledCcParts.suffix, txContext.signedProp, err) + triggerNextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, + Payload: []byte(err.Error()), Txid: msg.Txid} return } diff --git a/core/common/sysccprovider/sysccprovider.go b/core/common/sysccprovider/sysccprovider.go index 4e4ec22f5f5..bf177263c85 100644 --- a/core/common/sysccprovider/sysccprovider.go +++ b/core/common/sysccprovider/sysccprovider.go @@ -23,6 +23,10 @@ package sysccprovider type SystemChaincodeProvider interface { // IsSysCC returns true if the supplied chaincode is a system chaincode IsSysCC(name string) bool + + // IsSysCCAndNotInvokableCC2CC returns true if the supplied chaincode + // is a system chaincode and is not invokable through a cc2cc invocation + IsSysCCAndNotInvokableCC2CC(name string) bool } var sccFactory SystemChaincodeProviderFactory diff --git a/core/scc/importsysccs.go b/core/scc/importsysccs.go index 50e0c9a4a85..c1e45cdb529 100644 --- a/core/scc/importsysccs.go +++ b/core/scc/importsysccs.go @@ -41,7 +41,8 @@ var systemChaincodes = []*SystemChaincode{ Path: "github.com/hyperledger/fabric/core/scc/lscc", InitArgs: [][]byte{[]byte("")}, Chaincode: &lscc.LifeCycleSysCC{}, - InvokableExternal: true, // lccc is invoked to deploy new chaincodes + InvokableExternal: true, // lscc is invoked to deploy new chaincodes + InvokableCC2CC: true, // lscc can be invoked by other chaincodes }, { Enabled: true, @@ -116,6 +117,18 @@ func IsSysCCAndNotInvokable(name string) bool { return false } +// IsSysCCAndNotInvokableCC2CC returns true if the chaincode +// is a system chaincode and *CANNOT* be invoked through +// a cc2cc invocation +func IsSysCCAndNotInvokableCC2CC(name string) bool { + for _, sysCC := range systemChaincodes { + if sysCC.Name == name { + return !sysCC.InvokableCC2CC + } + } + return false +} + // MockRegisterSysCCs is used only for testing // This is needed to break import cycle func MockRegisterSysCCs(mockSysCCs []*SystemChaincode) []*SystemChaincode { diff --git a/core/scc/lscc/lscc_test.go b/core/scc/lscc/lscc_test.go index d2938c51f5f..87ed8ee4a95 100644 --- a/core/scc/lscc/lscc_test.go +++ b/core/scc/lscc/lscc_test.go @@ -51,6 +51,10 @@ func (c *mocksccProviderImpl) IsSysCC(name string) bool { return true } +func (c *mocksccProviderImpl) IsSysCCAndNotInvokableCC2CC(name string) bool { + return false +} + func register(stub *shim.MockStub, ccname string) error { args := [][]byte{[]byte("register"), []byte(ccname)} if res := stub.MockInvoke("1", args); res.Status != shim.OK { diff --git a/core/scc/sccproviderimpl.go b/core/scc/sccproviderimpl.go index cc546997852..552557c527d 100644 --- a/core/scc/sccproviderimpl.go +++ b/core/scc/sccproviderimpl.go @@ -44,3 +44,9 @@ type sccProviderImpl struct { func (c *sccProviderImpl) IsSysCC(name string) bool { return IsSysCC(name) } + +// IsSysCCAndNotInvokableCC2CC returns true if the supplied chaincode is +// ia system chaincode and it NOT nvokable through a cc2cc invocation +func (c *sccProviderImpl) IsSysCCAndNotInvokableCC2CC(name string) bool { + return IsSysCCAndNotInvokableCC2CC(name) +}