Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interop: use currently executing contract state for permissions check #3473

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/node-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ protocol-related settings described in the table below.
| --- | --- | --- | --- | --- |
| CommitteeHistory | map[uint32]uint32 | none | Number of committee members after the given height, for example `{0: 1, 20: 4}` sets up a chain with one committee member since the genesis and then changes the setting to 4 committee members at the height of 20. `StandbyCommittee` committee setting must have the number of keys equal or exceeding the highest value in this option. Blocks numbers where the change happens must be divisible by the old and by the new values simultaneously. If not set, committee size is derived from the `StandbyCommittee` setting and never changes. |
| Genesis | [Genesis](#Genesis-Configuration) | none | The set of genesis block settings including NeoGo-specific protocol extensions that should be enabled at the genesis block or during native contracts initialisation. |
| Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known hard-fork is applied from the zero blockchain height". The list of valid hard-fork names:<br>• `Aspidochelone` represents hard-fork introduced in [#2469](https://github.com/nspcc-dev/neo-go/pull/2469) (ported from the [reference](https://github.com/neo-project/neo/pull/2712)). It adjusts the prices of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. It also includes [#2519](https://github.com/nspcc-dev/neo-go/pull/2519) (ported from the [reference](https://github.com/neo-project/neo/pull/2749)) that adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. A special NeoGo-specific change is included as well for ContractManagement's update/deploy call flags behaviour to be compatible with pre-0.99.0 behaviour that was changed because of the [3.2.0 protocol change](https://github.com/neo-project/neo/pull/2653).<br>• `Basilisk` represents hard-fork introduced in [#3056](https://github.com/nspcc-dev/neo-go/pull/3056) (ported from the [reference](https://github.com/neo-project/neo/pull/2881)). It enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. It also includes [#3080](https://github.com/nspcc-dev/neo-go/pull/3080) (ported from the [reference](https://github.com/neo-project/neo/pull/2883)) that increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. It also includes [#3085](https://github.com/nspcc-dev/neo-go/pull/3085) (ported from the [reference](https://github.com/neo-project/neo/pull/2810)) that enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. <br>• `Cockatrice` represents hard-fork introduced in [#3402](https://github.com/nspcc-dev/neo-go/pull/3402) (ported from the [reference](https://github.com/neo-project/neo/pull/2942)). Initially it is introduced along with the ability to update native contracts. This hard-fork also includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract introduced in [#3301](https://github.com/nspcc-dev/neo-go/pull/3301) (ported from the [reference](https://github.com/neo-project/neo/pull/2925)) and `getCommitteeAddress` of native NeoToken contract inctroduced in [#3362](https://github.com/nspcc-dev/neo-go/pull/3362) (ported from the [reference](https://github.com/neo-project/neo/pull/3154)).<br>• `Domovoi` represents hard-fork introduced in [#3476](https://github.com/nspcc-dev/neo-go/pull/3476) (ported from the [reference](https://github.com/neo-project/neo/pull/3290)). Currently this hardfork doesn't enable any additional logic. |
| Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known hard-fork is applied from the zero blockchain height". The list of valid hard-fork names:<br>• `Aspidochelone` represents hard-fork introduced in [#2469](https://github.com/nspcc-dev/neo-go/pull/2469) (ported from the [reference](https://github.com/neo-project/neo/pull/2712)). It adjusts the prices of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. It also includes [#2519](https://github.com/nspcc-dev/neo-go/pull/2519) (ported from the [reference](https://github.com/neo-project/neo/pull/2749)) that adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. A special NeoGo-specific change is included as well for ContractManagement's update/deploy call flags behaviour to be compatible with pre-0.99.0 behaviour that was changed because of the [3.2.0 protocol change](https://github.com/neo-project/neo/pull/2653).<br>• `Basilisk` represents hard-fork introduced in [#3056](https://github.com/nspcc-dev/neo-go/pull/3056) (ported from the [reference](https://github.com/neo-project/neo/pull/2881)). It enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. It also includes [#3080](https://github.com/nspcc-dev/neo-go/pull/3080) (ported from the [reference](https://github.com/neo-project/neo/pull/2883)) that increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. It also includes [#3085](https://github.com/nspcc-dev/neo-go/pull/3085) (ported from the [reference](https://github.com/neo-project/neo/pull/2810)) that enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. <br>• `Cockatrice` represents hard-fork introduced in [#3402](https://github.com/nspcc-dev/neo-go/pull/3402) (ported from the [reference](https://github.com/neo-project/neo/pull/2942)). Initially it is introduced along with the ability to update native contracts. This hard-fork also includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract introduced in [#3301](https://github.com/nspcc-dev/neo-go/pull/3301) (ported from the [reference](https://github.com/neo-project/neo/pull/2925)) and `getCommitteeAddress` of native NeoToken contract inctroduced in [#3362](https://github.com/nspcc-dev/neo-go/pull/3362) (ported from the [reference](https://github.com/neo-project/neo/pull/3154)).<br>• `Domovoi` represents hard-fork introduced in [#3476](https://github.com/nspcc-dev/neo-go/pull/3476) (ported from the [reference](https://github.com/neo-project/neo/pull/3290)). This hard-fork makes the node use executing contract state for the contract call permissions check instead of the state stored in the native Management. This change was introduced in [#3473](https://github.com/nspcc-dev/neo-go/pull/3473) and ported to the [reference](https://github.com/neo-project/neo/pull/3290).|
| Magic | `uint32` | `0` | Magic number which uniquely identifies Neo network. |
| MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. |
| MaxBlockSystemFee | `int64` | `900000000000` | Maximum overall transactions system fee per block. |
Expand Down
6 changes: 5 additions & 1 deletion pkg/config/hardfork.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ const (
// https://github.com/neo-project/neo/pull/3154).
HFCockatrice // Cockatrice
// HFDomovoi represents hard-fork introduced in #3476 (ported from
// https://github.com/neo-project/neo/pull/3290).
// https://github.com/neo-project/neo/pull/3290). It makes the`node use
// executing contract state for the contract call permissions check instead
// of the state stored in the native Management. This change was introduced
// in [#3473](https://github.com/nspcc-dev/neo-go/pull/3473) and ported to
// the [reference](https://github.com/neo-project/neo/pull/3290).
HFDomovoi // Domovoi
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
// before hfLast.
Expand Down
15 changes: 11 additions & 4 deletions pkg/core/interop/contract/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math/big"
"strings"

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
Expand Down Expand Up @@ -77,12 +78,18 @@ func callInternal(ic *interop.Context, cs *state.Contract, name string, f callfl
if md.Safe {
f &^= (callflag.WriteStates | callflag.AllowNotify)
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(cs.Hash, &cs.Manifest, name) {
return errors.New("disallowed method call")
var mfst *manifest.Manifest
if ic.IsHardforkEnabled(config.HFDomovoi) {
mfst = ctx.GetManifest()
} else {
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
if err == nil {
mfst = &curr.Manifest
}
}
if mfst != nil && !mfst.CanCall(cs.Hash, &cs.Manifest, name) {
return errors.New("disallowed method call")
}
}
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn, isDynamic, false)
}
Expand Down
112 changes: 112 additions & 0 deletions pkg/core/interop/contract/call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
Expand Down Expand Up @@ -173,6 +174,117 @@ func TestCall(t *testing.T) {
})
}

func TestSystemContractCall_Permissions(t *testing.T) {
check := func(t *testing.T, cfg func(*config.Blockchain), shouldUpdateFail bool) {
bc, acc := chain.NewSingleWithCustomConfig(t, cfg)
e := neotest.NewExecutor(t, bc, acc, acc)

// Contract A has an unsafe method.
srcA := `package contractA
func RetOne() int {
return 1
}`
ctrA := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcA), &compiler.Options{
NoEventsCheck: true,
NoPermissionsCheck: true,
Name: "contractA",
})
e.DeployContract(t, ctrA, nil)

var hashAStr string
for i := 0; i < util.Uint160Size; i++ {
hashAStr += fmt.Sprintf("%#x", ctrA.Hash[i])
if i != util.Uint160Size-1 {
hashAStr += ", "
}
}

// Contract B has a method that calls contract's A and another update method that
// calls contract's A after B's update.
srcB := `package contractB
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
)
func CallRetOne() int {
res := contract.Call(interop.Hash160{` + hashAStr + `}, "retOne", contract.All).(int)
return res
}
func Update(nef []byte, manifest []byte) int {
management.Update(nef, manifest)
res := contract.Call(interop.Hash160{` + hashAStr + `}, "retOne", contract.All).(int)
return res
}`
ctrB := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcB), &compiler.Options{
Name: "contractB",
NoEventsCheck: true,
NoPermissionsCheck: true,
Permissions: []manifest.Permission{
{
Contract: manifest.PermissionDesc{Type: manifest.PermissionHash, Value: ctrA.Hash},
Methods: manifest.WildStrings{Value: []string{"retOne"}},
},
{
Methods: manifest.WildStrings{Value: []string{"update"}},
},
},
})
e.DeployContract(t, ctrB, nil)
ctrBInvoker := e.ValidatorInvoker(ctrB.Hash)

// ctrBUpdated differs from ctrB in that it has no permission to call retOne method of ctrA
ctrBUpdated := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcB), &compiler.Options{
Name: "contractB",
NoEventsCheck: true,
NoPermissionsCheck: true,
Permissions: []manifest.Permission{
{
Contract: manifest.PermissionDesc{Type: manifest.PermissionHash, Value: ctrA.Hash},
Methods: manifest.WildStrings{Value: []string{}},
},
{
Methods: manifest.WildStrings{Value: []string{"update"}},
},
},
})

// Call to A before B update should HALT.
ctrBInvoker.Invoke(t, stackitem.Make(1), "callRetOne")

// Call to A in the same context as B update should HALT.
n, err := ctrBUpdated.NEF.Bytes()
require.NoError(t, err)
m, err := json.Marshal(ctrBUpdated.Manifest)
require.NoError(t, err)
if shouldUpdateFail {
ctrBInvoker.InvokeFail(t, "System.Contract.Call failed: disallowed method call", "update", n, m)
} else {
ctrBInvoker.Invoke(t, stackitem.Make(1), "update", n, m)
}

// If contract is updated, then all to A after B update should FAULT (manifest
// is updated, no permission to call retOne method).
if !shouldUpdateFail {
ctrBInvoker.InvokeFail(t, "System.Contract.Call failed: disallowed method call", "callRetOne")
}
}

// Pre-Domovoi behaviour: an updated contract state is used for permissions check.
check(t, func(cfg *config.Blockchain) {
cfg.Hardforks = map[string]uint32{
config.HFDomovoi.String(): 100500,
}
}, true)

// Post-Domovoi behaviour: an executing contract state is used for permissions check.
check(t, func(cfg *config.Blockchain) {
cfg.Hardforks = map[string]uint32{
config.HFDomovoi.String(): 0,
}
}, false)
}

func TestLoadToken(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
Expand Down
Loading