From 4004ba139ab1fccb2987faea9c244f8eb46f181a Mon Sep 17 00:00:00 2001 From: Mikael VALLENET Date: Tue, 26 Nov 2024 16:06:40 +0100 Subject: [PATCH] feat(gnovm): forbid importing realms in packages (#3042) Closes #3040 50% of the work comes from @harry-hov's PR #1393 (let's repay to Caesar what belongs to Caesar) :rocket: Notable additions: - handle different domains (e.g github.com/p/demo/...) - skip non ``.gno`` files (LICENSE, README, ...) or empty files
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [x] Provided any useful hints for running manual tests
--------- Co-authored-by: n0izn0iz Co-authored-by: Morgan Co-authored-by: Morgan --- examples/gno.land/p/demo/groups/gno.mod | 5 +--- examples/gno.land/p/demo/groups/groups.gno | 8 ----- examples/gno.land/p/demo/tests/gno.mod | 1 - examples/gno.land/p/demo/tests/tests.gno | 13 --------- .../gno.land/p/demo/tests/z0_filetest.gno | 16 ---------- .../gnoland/testdata/assertorigincall.txtar | 29 ++++++++++--------- gno.land/cmd/gnoland/testdata/prevrealm.txtar | 22 +++++++------- gnovm/pkg/gnolang/helpers.go | 12 +++++++- gnovm/pkg/gnolang/preprocess.go | 7 +++++ gnovm/tests/files/import11.gno | 13 +++++++++ gnovm/tests/files/zrealm_crossrealm11.gno | 11 ++----- 11 files changed, 60 insertions(+), 77 deletions(-) delete mode 100644 examples/gno.land/p/demo/groups/groups.gno delete mode 100644 examples/gno.land/p/demo/tests/z0_filetest.gno create mode 100644 gnovm/tests/files/import11.gno diff --git a/examples/gno.land/p/demo/groups/gno.mod b/examples/gno.land/p/demo/groups/gno.mod index f0749e3f411..cf33d0ce74b 100644 --- a/examples/gno.land/p/demo/groups/gno.mod +++ b/examples/gno.land/p/demo/groups/gno.mod @@ -1,6 +1,3 @@ module gno.land/p/demo/groups -require ( - gno.land/p/demo/rat v0.0.0-latest - gno.land/r/demo/boards v0.0.0-latest -) +require gno.land/p/demo/rat v0.0.0-latest diff --git a/examples/gno.land/p/demo/groups/groups.gno b/examples/gno.land/p/demo/groups/groups.gno deleted file mode 100644 index fcf77dd2a74..00000000000 --- a/examples/gno.land/p/demo/groups/groups.gno +++ /dev/null @@ -1,8 +0,0 @@ -package groups - -import "gno.land/r/demo/boards" - -// TODO implement something and test. -type Group struct { - Board *boards.Board -} diff --git a/examples/gno.land/p/demo/tests/gno.mod b/examples/gno.land/p/demo/tests/gno.mod index d3d796f76f8..8a19acdbb18 100644 --- a/examples/gno.land/p/demo/tests/gno.mod +++ b/examples/gno.land/p/demo/tests/gno.mod @@ -3,5 +3,4 @@ module gno.land/p/demo/tests require ( gno.land/p/demo/tests/subtests v0.0.0-latest gno.land/p/demo/uassert v0.0.0-latest - gno.land/r/demo/tests v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index 43732d82dac..ffad5b8c8cd 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -4,19 +4,10 @@ import ( "std" psubtests "gno.land/p/demo/tests/subtests" - "gno.land/r/demo/tests" - rtests "gno.land/r/demo/tests" ) const World = "world" -// IncCounter demonstrates that it's possible to call a realm function from -// a package. So a package can potentially write into the store, by calling -// an other realm. -func IncCounter() { - tests.IncCounter() -} - func CurrentRealmPath() string { return std.CurrentRealm().PkgPath() } @@ -64,10 +55,6 @@ func GetPSubtestsPrevRealm() std.Realm { return psubtests.GetPrevRealm() } -func GetRTestsGetPrevRealm() std.Realm { - return rtests.GetPrevRealm() -} - // Warning: unsafe pattern. func Exec(fn func()) { fn() diff --git a/examples/gno.land/p/demo/tests/z0_filetest.gno b/examples/gno.land/p/demo/tests/z0_filetest.gno deleted file mode 100644 index b788eaf398f..00000000000 --- a/examples/gno.land/p/demo/tests/z0_filetest.gno +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - ptests "gno.land/p/demo/tests" - rtests "gno.land/r/demo/tests" -) - -func main() { - println(rtests.Counter()) - ptests.IncCounter() - println(rtests.Counter()) -} - -// Output: -// 0 -// 1 diff --git a/gno.land/cmd/gnoland/testdata/assertorigincall.txtar b/gno.land/cmd/gnoland/testdata/assertorigincall.txtar index 1315f23cc95..62d660a9215 100644 --- a/gno.land/cmd/gnoland/testdata/assertorigincall.txtar +++ b/gno.land/cmd/gnoland/testdata/assertorigincall.txtar @@ -9,18 +9,18 @@ # | 4 | | through /r/foo | myrealm.A() | PANIC | # | 5 | | | myrealm.B() | pass | # | 6 | | | myrealm.C() | PANIC | -# | 7 | | through /p/demo/bar | myrealm.A() | PANIC | -# | 8 | | | myrealm.B() | pass | -# | 9 | | | myrealm.C() | PANIC | +# | 7 | | through /p/demo/bar | bar.A() | PANIC | +# | 8 | | | bar.B() | pass | +# | 9 | | | bar.C() | PANIC | # | 10 | MsgRun | wallet direct | myrealm.A() | PANIC | # | 11 | | | myrealm.B() | pass | # | 12 | | | myrealm.C() | PANIC | # | 13 | | through /r/foo | myrealm.A() | PANIC | # | 14 | | | myrealm.B() | pass | # | 15 | | | myrealm.C() | PANIC | -# | 16 | | through /p/demo/bar | myrealm.A() | PANIC | -# | 17 | | | myrealm.B() | pass | -# | 18 | | | myrealm.C() | PANIC | +# | 16 | | through /p/demo/bar | bar.A() | PANIC | +# | 17 | | | bar.B() | pass | +# | 18 | | | bar.C() | PANIC | # | 19 | MsgCall | wallet direct | std.AssertOriginCall() | pass | # | 20 | MsgRun | wallet direct | std.AssertOriginCall() | PANIC | @@ -57,15 +57,15 @@ stdout 'OK!' stderr 'invalid non-origin call' ## remove due to update to maketx call can only call realm (case 7,8,9) -## 7. MsgCall -> p/demo/bar.A -> myrlm.A: PANIC +## 7. MsgCall -> p/demo/bar.A: PANIC ## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' -## 8. MsgCall -> p/demo/bar.B -> myrlm.B: PASS +## 8. MsgCall -> p/demo/bar.B: PASS ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout 'OK!' -## 9. MsgCall -> p/demo/bar.C -> myrlm.C: PANIC +## 9. MsgCall -> p/demo/bar.C: PANIC ## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' @@ -152,18 +152,19 @@ func C() { -- p/demo/bar/bar.gno -- package bar -import "gno.land/r/myrlm" +import "std" func A() { - myrlm.A() + C() } func B() { - myrlm.B() + if false { + C() + } } - func C() { - myrlm.C() + std.AssertOriginCall() } -- run/myrlmA.gno -- package main diff --git a/gno.land/cmd/gnoland/testdata/prevrealm.txtar b/gno.land/cmd/gnoland/testdata/prevrealm.txtar index 7a0d994a686..4a7cece6d62 100644 --- a/gno.land/cmd/gnoland/testdata/prevrealm.txtar +++ b/gno.land/cmd/gnoland/testdata/prevrealm.txtar @@ -8,14 +8,14 @@ # | 2 | | | myrlm.B() | user address | # | 3 | | through /r/foo | myrlm.A() | r/foo | # | 4 | | | myrlm.B() | r/foo | -# | 5 | | through /p/demo/bar | myrlm.A() | user address | -# | 6 | | | myrlm.B() | user address | +# | 5 | | through /p/demo/bar | bar.A() | user address | +# | 6 | | | bar.B() | user address | # | 7 | MsgRun | wallet direct | myrlm.A() | user address | # | 8 | | | myrlm.B() | user address | # | 9 | | through /r/foo | myrlm.A() | r/foo | # | 10 | | | myrlm.B() | r/foo | -# | 11 | | through /p/demo/bar | myrlm.A() | user address | -# | 12 | | | myrlm.B() | user address | +# | 11 | | through /p/demo/bar | bar.A() | user address | +# | 12 | | | bar.B() | user address | # | 13 | MsgCall | wallet direct | std.PrevRealm() | user address | # | 14 | MsgRun | wallet direct | std.PrevRealm() | user address | @@ -50,11 +50,11 @@ gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wan stdout ${RFOO_ADDR} ## remove due to update to maketx call can only call realm (case 5, 6, 13) -## 5. MsgCall -> p/demo/bar.A -> myrlm.A: user address +## 5. MsgCall -> p/demo/bar.A: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout ${USER_ADDR_test1} -## 6. MsgCall -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address +## 6. MsgCall -> p/demo/bar.B: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout ${USER_ADDR_test1} @@ -74,11 +74,11 @@ stdout ${RFOO_ADDR} gnokey maketx run -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno stdout ${RFOO_ADDR} -## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address +## 11. MsgRun -> p/demo/bar.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno stdout ${USER_ADDR_test1} -## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address +## 12. MsgRun -> p/demo/bar.B: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno stdout ${USER_ADDR_test1} @@ -117,14 +117,14 @@ func B() string { -- p/demo/bar/bar.gno -- package bar -import "gno.land/r/myrlm" +import "std" func A() string { - return myrlm.A() + return std.PrevRealm().Addr().String() } func B() string { - return myrlm.B() + return A() } -- run/myrlmA.gno -- package main diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index c6f7e696ea4..d3a8485ee17 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -12,7 +12,10 @@ import ( // RealmPathPrefix is the prefix used to identify pkgpaths which are meant to // be realms and as such to have their state persisted. This is used by [IsRealmPath]. -const RealmPathPrefix = "gno.land/r/" +const ( + RealmPathPrefix = "gno.land/r/" + PackagePathPrefix = "gno.land/p/" +) // ReGnoRunPath is the path used for realms executed in maketx run. // These are not considered realms, as an exception to the RealmPathPrefix rule. @@ -26,6 +29,13 @@ func IsRealmPath(pkgPath string) bool { !ReGnoRunPath.MatchString(pkgPath) } +// IsPurePackagePath determines whether the given pkgpath is for a published Gno package. +// It only considers "pure" those starting with gno.land/p/, so it returns false for +// stdlib packages and MsgRun paths. +func IsPurePackagePath(pkgPath string) bool { + return strings.HasPrefix(pkgPath, PackagePathPrefix) +} + // IsStdlib determines whether s is a pkgpath for a standard library. func IsStdlib(s string) bool { // NOTE(morgan): this is likely to change in the future as we add support for diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 4b556604f0b..53c187342a6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -174,6 +174,13 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) { case *ImportDecl: nx := &n.NameExpr nn := nx.Name + loc := last.GetLocation() + // NOTE: imports from "pure packages" are actually sometimes + // allowed, most notably in MsgRun and filetests; IsPurePackagePath + // returns false in these cases. + if IsPurePackagePath(loc.PkgPath) && IsRealmPath(n.PkgPath) { + panic(fmt.Sprintf("pure package path %q cannot import realm path %q", loc.PkgPath, n.PkgPath)) + } if nn == "." { panic("dot imports not allowed in gno") } diff --git a/gnovm/tests/files/import11.gno b/gnovm/tests/files/import11.gno new file mode 100644 index 00000000000..594e9f10698 --- /dev/null +++ b/gnovm/tests/files/import11.gno @@ -0,0 +1,13 @@ +// PKGPATH: gno.land/p/demo/bar +package bar + +import ( + "gno.land/r/demo/tests" +) + +func main() { + println(tests.Counter()) +} + +// Error: +// gno.land/p/demo/bar/files/import11.gno:5:2: pure package path "gno.land/p/demo/bar" cannot import realm path "gno.land/r/demo/tests" diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index e6f33c50654..5936743ddc6 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -2,10 +2,11 @@ package crossrealm_test import ( + "std" + ptests "gno.land/p/demo/tests" "gno.land/p/demo/ufmt" rtests "gno.land/r/demo/tests" - "std" ) func getPrevRealm() std.Realm { @@ -64,10 +65,6 @@ func main() { callStackAdd: " -> r/demo/tests -> r/demo/tests/subtests", callerFn: rtests.GetRSubtestsPrevRealm, }, - { - callStackAdd: " -> p/demo/tests -> r/demo/tests", - callerFn: ptests.GetRTestsGetPrevRealm, - }, } println("---") // needed to have space prefixes @@ -140,7 +137,3 @@ func printColumns(left, right string) { // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests -// user1.gno -> r/crossrealm_test.main -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test -// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test -// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test