From a70e880fad13dbd8cc1e11a4be05edc3780bce68 Mon Sep 17 00:00:00 2001 From: Albert Le Batteux Date: Mon, 3 Apr 2023 19:22:34 +0100 Subject: [PATCH] feat(stdlibs): add std.GetCaller --- .../p/demo/tests/subtests/subtests.gno | 17 +++ examples/gno.land/p/demo/tests/tests.gno | 24 +++- .../gno.land/r/demo/tests/fake20/fake20.gno | 122 ++++++++++++++++++ .../r/demo/tests/phishing/phishing.gno | 4 + .../r/demo/tests/subtests/subtests.gno | 17 +++ examples/gno.land/r/demo/tests/tests.gno | 30 ++++- .../r/demo/tests/unsaferealm/unsaferealm.gno | 52 ++++++++ .../tests/unsaferealm/unsaferealm_test.gno | 9 ++ gnovm/pkg/gnolang/frame.go | 4 +- gnovm/stdlibs/stdlibs.go | 38 ++++++ gnovm/tests/file.go | 5 +- gnovm/tests/files/zrealm_crossrealm11.gno | 73 +++++++++++ .../zrealm_crossrealm_exploit1_stdlibs.gno | 60 +++++++++ 13 files changed, 450 insertions(+), 5 deletions(-) create mode 100644 examples/gno.land/p/demo/tests/subtests/subtests.gno create mode 100644 examples/gno.land/r/demo/tests/fake20/fake20.gno create mode 100644 examples/gno.land/r/demo/tests/phishing/phishing.gno create mode 100644 examples/gno.land/r/demo/tests/subtests/subtests.gno create mode 100644 examples/gno.land/r/demo/tests/unsaferealm/unsaferealm.gno create mode 100644 examples/gno.land/r/demo/tests/unsaferealm/unsaferealm_test.gno create mode 100644 gnovm/tests/files/zrealm_crossrealm11.gno create mode 100644 gnovm/tests/files/zrealm_crossrealm_exploit1_stdlibs.gno diff --git a/examples/gno.land/p/demo/tests/subtests/subtests.gno b/examples/gno.land/p/demo/tests/subtests/subtests.gno new file mode 100644 index 00000000000..c85549a7992 --- /dev/null +++ b/examples/gno.land/p/demo/tests/subtests/subtests.gno @@ -0,0 +1,17 @@ +package subtests + +import ( + "std" +) + +func CurrentRealmPath() string { + return std.CurrentRealmPath() +} + +func GetCaller() std.Address { + return std.GetCaller() +} + +func Exec(fn func()) { + fn() +} diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index c4b8f93d52d..6e05ad288ac 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -1,6 +1,10 @@ package tests -import "std" +import ( + "std" + + psubtests "gno.land/p/demo/tests/subtests" +) func CurrentRealmPath() string { return std.CurrentRealmPath() @@ -40,3 +44,21 @@ func ModifyTestRealmObject2b() { func ModifyTestRealmObject2c() { SomeValue3.Field = "modified" } + +func GetCaller() std.Address { + return std.GetCaller() +} + +func GetSubtestsCaller() std.Address { + return psubtests.GetCaller() +} + +func PrintCallers() { + println("p/TEST : ORIGCALL:", std.GetOrigCaller()) + println("p/TEST : PKG ADDR:", std.GetOrigPkgAddr()) + println("p/TEST : CALLER :", std.GetCaller()) +} + +func Exec(fn func()) { + fn() +} diff --git a/examples/gno.land/r/demo/tests/fake20/fake20.gno b/examples/gno.land/r/demo/tests/fake20/fake20.gno new file mode 100644 index 00000000000..493f234bc0f --- /dev/null +++ b/examples/gno.land/r/demo/tests/fake20/fake20.gno @@ -0,0 +1,122 @@ +package fake20 + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" +) + +var ( + foo *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // TODO: helper to change admin +) + +func Init() { + foo = grc20.NewAdminToken("Fake", "FAKE", 4) + foo.Mint(admin, 1000000*10000) // @administrator (1M) + foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k) + foo.Mint("g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf", 10000*10000) // @gno.land/r/demo/tests/unsaferealm +} + +func init() { + Init() +} + + +// method proxies as public functions. +// + +// getters. + +func GetCaller() std.Address { + return std.GetCallerAt(1) + // return std.GetOrigPkgAddr() +} + +func TotalSupply() uint64 { + return foo.TotalSupply() +} + +func BalanceOf(owner std.Address) uint64 { + balance, err := foo.BalanceOf(owner) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender std.Address) uint64 { + allowance, err := foo.Allowance(owner, spender) + if err != nil { + panic(err) + } + return allowance +} + +// setters. + +func Transfer(to std.Address, amount uint64) { + caller := std.GetCaller() + println("transfering", amount, "from:", caller, "to:", to) + foo.Transfer(caller, to, amount) +} + +func Approve(spender std.Address, amount uint64) { + caller := std.GetCaller() + foo.Approve(caller, spender, amount) +} + +func TransferFrom(from, to std.Address, amount uint64) { + caller := std.GetCaller() + foo.TransferFrom(caller, from, to, amount) +} + +// faucet. + +func Faucet() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.GetCaller() + foo.Mint(caller, 1000*10000) // 1k +} + +// administration. + +func Mint(address std.Address, amount uint64) { + caller := std.GetCaller() + assertIsAdmin(caller) + foo.Mint(address, amount) +} + +func Burn(address std.Address, amount uint64) { + caller := std.GetCaller() + assertIsAdmin(caller) + foo.Burn(address, amount) +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return foo.RenderHome() + case c == 2 && parts[0] == "balance": + owner := std.Address(parts[1]) + balance, _ := foo.BalanceOf(owner) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/examples/gno.land/r/demo/tests/phishing/phishing.gno b/examples/gno.land/r/demo/tests/phishing/phishing.gno new file mode 100644 index 00000000000..d3970459b80 --- /dev/null +++ b/examples/gno.land/r/demo/tests/phishing/phishing.gno @@ -0,0 +1,4 @@ +package phising + +func GetMillionaire() { +} diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno new file mode 100644 index 00000000000..c85549a7992 --- /dev/null +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -0,0 +1,17 @@ +package subtests + +import ( + "std" +) + +func CurrentRealmPath() string { + return std.CurrentRealmPath() +} + +func GetCaller() std.Address { + return std.GetCaller() +} + +func Exec(fn func()) { + fn() +} diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index dde1e7cad75..bb211ea5734 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -1,6 +1,10 @@ package tests -import "std" +import ( + "std" + + "gno.land/r/demo/tests/subtests" +) func CurrentRealmPath() string { return std.CurrentRealmPath() @@ -51,3 +55,27 @@ func ModTestNodes() { func PrintTestNodes() { println(gTestNode2.Child.Name) } + +func GetCaller() std.Address { + return std.GetCaller() +} + +func GetSubtestsCaller() std.Address { + return subtests.GetCaller() +} + +func ExecFromTest() { + Exec(func() { + println("xxx -> r/demo/tests -> r/demo/tests.Exec:", std.GetCaller()) + }) +} + +func Exec(fn func()) { + fn() +} + +func PrintCallers() { + println("p/TEST : ORIGCALL:", std.GetOrigCaller()) + println("p/TEST : PKG ADDR:", std.GetOrigPkgAddr()) + println("p/TEST : CALLER :", std.GetCaller()) +} diff --git a/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm.gno b/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm.gno new file mode 100644 index 00000000000..d5c5ef63944 --- /dev/null +++ b/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm.gno @@ -0,0 +1,52 @@ +package unsaferealm + +import ( + "std" + + "gno.land/p/demo/grc/grc20" +) + +var ( + foo *grc20.AdminToken + Owner = std.Address("g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf") +) + +func init() { + foo = grc20.NewAdminToken("Fake", "FAKE", 4) + + // std.TestDerivePkgAddr("gno.land/r/demo/tests/unsaferealm") + // foo.Mint("g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf", 1000000*10000) + foo.Mint(Owner, 1000000*10000) + // foo.Mint(std.GetOrigPkgAddr(), 1000000*10000) +} + +/* +** Some grc20 functions + */ + +func GetCaller() std.Address { + return std.GetCallerAt(1) +} + +func BalanceOf(owner std.Address) uint64 { + balance, err := foo.BalanceOf(owner) + if err != nil { + panic(err) + } + + return balance +} + +func Transfer(to std.Address, amount uint64) { + caller := std.GetCaller() + println("transfering", amount, "from:", caller, "to:", to) + foo.Transfer(caller, to, amount) +} + +/* +** Realm unsafe functions + */ + +func Do(fn func()) { + fn() +} diff --git a/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm_test.gno b/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm_test.gno new file mode 100644 index 00000000000..988dd16c238 --- /dev/null +++ b/examples/gno.land/r/demo/tests/unsaferealm/unsaferealm_test.gno @@ -0,0 +1,9 @@ +package unsaferealm + +import ( + "testing" +) + +func TestUnsafeRealm(t *testing.T) { + println("Hello") +} diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 7e3dc92dd4a..7f87fa26097 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -1,6 +1,8 @@ package gnolang -import "fmt" +import ( + "fmt" +) //---------------------------------------- // (runtime) Frame diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 495482cfad2..7b110ea396a 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -285,6 +285,44 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) { m.PushValue(res0) }, ) + pn.DefineNative("GetCaller", + gno.Flds( // params + ), + gno.Flds( // results + "", "Address", + ), + func(m *gno.Machine) { + ctx := m.Context.(ExecContext) + + lastCaller := ctx.OrigCaller + + if m.NumFrames() <= 2 { + lastCaller = ctx.OrigCaller + } else { + + for i := m.NumFrames() - 1; i > 0; i-- { + frameA := m.Frames[i] + frameB := m.Frames[i-1] + if !frameB.LastPackage.IsRealm() { + continue + } + if !frameA.LastPackage.IsRealm() && !frameB.LastPackage.IsRealm() { + continue + } + lastCaller = frameB.LastPackage.GetPkgAddr().Bech32() + break + } + } + res0 := gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(lastCaller), + ) + addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) + res0.T = addrT + m.PushValue(res0) + }, + ) pn.DefineNative("GetOrigPkgAddr", gno.Flds( // params ), diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 10eb0ddb62c..9a8a658862c 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -34,7 +34,7 @@ func TestMachine(store gno.Store, stdout io.Writer, pkgPath string) *gno.Machine func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAlloc int64, send std.Coins) *gno.Machine { // FIXME: create a better package to manage this, with custom constructors pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called. - caller := gno.DerivePkgAddr(pkgPath) // NOTE: for the purpose of testing, the caller is generally the "main" package, same as pkgAddr. + caller := gno.DerivePkgAddr("user1.gno") // NOTE: for the purpose of testing, the caller is generally the "main" package, same as pkgAddr. pkgCoins := std.MustParseCoins("200000000ugnot").Add(send) // >= send. banker := newTestBanker(pkgAddr.Bech32(), pkgCoins) ctx := stdlibs.ExecContext{ @@ -295,7 +295,8 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { if resWanted == "" { panic(fmt.Sprintf("fail on %s: got unexpected output: %s", path, res)) } else { - panic(fmt.Sprintf("fail on %s: got:\n%s\n\nwant:\n%s\n", path, res, resWanted)) + fmt.Printf("fail on %s: got:\n%s\n\nwant:\n%s\n", path, res, resWanted) + os.Exit(1) } } } diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno new file mode 100644 index 00000000000..b3a485b9e41 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -0,0 +1,73 @@ +// PKGPATH: gno.land/r/crossrealm_test +package crossrealm_test + +import ( + "std" + + ptests "gno.land/p/demo/tests" + rtests "gno.land/r/demo/tests" +) + +func Exec(fn func()) { + fn() +} + +func main() { + println(`[DEBUG] + user1.gno: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm + crossrealm_test: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p + gno.land/p/demo/tests: g1lc7c8nv62nqyyhhxe88tpxx786gwq68prx3f6e + gno.land/r/demo/tests: g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq + `) + + println("user1.gno -> r/crossrealm_test:", std.GetCaller()) + + println("user1.gno -> r/crossrealm_test -> p/demo/tests:", ptests.GetCaller()) + + println("user1.gno -> r/crossrealm_test -> r/demo/tests:", rtests.GetCaller()) + + println("user1.gno -> r/crossrealm_test -> r/demo/tests -> r/demo/tests/subtests", rtests.GetSubtestsCaller()) + + println("user1.gno -> r/crossrealm_test -> p/demo/tests -> p/demo/tests/subtests", ptests.GetSubtestsCaller()) + + // let's try to exploit an unsafe realm + rtests.Exec(func() { + // caller is `r/demo/tests` + println("user1.gno -> r/crossrealm_test -> r/demo/tests.Exec", std.GetCaller()) + + + // NOTE(WARNING): this one is a weird behaviour, because the caller returned is crossrealm_test + // caller is `crossrealm_test` + println("/!\\ user1.gno -> r/crossrealm_test -> r/demo/tests.Exec -> p/demo/tests:", ptests.GetCaller()) + }) + + ptests.Exec(func() { + println("user1.gno -> r/crossrealm_test -> p/demo/tests.Exec: ", std.GetCaller()) + println("user1.gno -> r/crossrealm_test -> p/demo/tests.Exec -> p/demo/tests:", ptests.GetCaller()) + }) + + rtests.ExecFromTest() + + Exec(func() { + println("user1.gno -> r/crossrealm_test.Exec:", std.GetCaller()) + }) +} + +// Output: +// [DEBUG] +// user1.gno: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// crossrealm_test: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// gno.land/p/demo/tests: g1lc7c8nv62nqyyhhxe88tpxx786gwq68prx3f6e +// gno.land/r/demo/tests: g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// +// user1.gno -> r/crossrealm_test: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// user1.gno -> r/crossrealm_test -> p/demo/tests: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// user1.gno -> r/crossrealm_test -> r/demo/tests: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// user1.gno -> r/crossrealm_test -> r/demo/tests -> r/demo/tests/subtests g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// user1.gno -> r/crossrealm_test -> p/demo/tests -> p/demo/tests/subtests g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// user1.gno -> r/crossrealm_test -> r/demo/tests.Exec g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// /!\ user1.gno -> r/crossrealm_test -> r/demo/tests.Exec -> p/demo/tests: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// user1.gno -> r/crossrealm_test -> p/demo/tests.Exec: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// user1.gno -> r/crossrealm_test -> p/demo/tests.Exec -> p/demo/tests: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p +// xxx -> r/demo/tests -> r/demo/tests.Exec: g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// user1.gno -> r/crossrealm_test.Exec: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p diff --git a/gnovm/tests/files/zrealm_crossrealm_exploit1_stdlibs.gno b/gnovm/tests/files/zrealm_crossrealm_exploit1_stdlibs.gno new file mode 100644 index 00000000000..d2fb8fc7564 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm_exploit1_stdlibs.gno @@ -0,0 +1,60 @@ +// PKGPATH: gno.land/r/crossrealm_test +package crossrealm_test + +import ( + "std" + + "gno.land/r/demo/tests" + "gno.land/r/demo/tests/unsaferealm" + "gno.land/r/demo/tests/fake20" +) + +func main() { + println("crossrealm caller :", std.GetCaller(), "balance:", unsaferealm.BalanceOf(std.GetCaller())) + println("unsaferealm caller:", unsaferealm.GetCaller(), "balance:", unsaferealm.BalanceOf(unsaferealm.GetCaller())) + println("fake20 caller :", fake20.GetCaller(), "balance:", unsaferealm.BalanceOf(fake20.GetCaller())) + println("======================") + + addr := std.GetCaller() + // println("balance:", unsaferealm.BalanceOf(addr)) + + // Test to exploit from the Do function steal money from unsaferealm treasury + unsaferealm.Do(func() { + println("transfering 1.", std.GetCaller()) + unsaferealm.Transfer(addr, 100000) + }) + + println("balance:", unsaferealm.BalanceOf(addr)) + println("======================") + + // Test to exploit from the Do function steal money from unsaferealm treasury + unsaferealm.Do(func() { + println("transfering 2.", std.GetCaller()) + fake20.Transfer(addr, 100000) + unsaferealm.Do(func() { + fake20.Transfer(addr, 100000) + }) + }) + + println("======================") + println("crossrealm caller :", std.GetCaller(), "balance:", unsaferealm.BalanceOf(std.GetCaller())) + println("unsaferealm caller:", unsaferealm.GetCaller(), "balance:", unsaferealm.BalanceOf(unsaferealm.GetCaller())) + println("fake20 caller :", fake20.GetCaller(), "balance:", unsaferealm.BalanceOf(fake20.GetCaller())) +} + +// Output: +// crossrealm caller : g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm balance: 0 +// unsaferealm caller: g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf balance: 10000000000 +// fake20 caller : g1zjzuzhywuxrk3cdvm7ma33vu4x2xyunxhzslu9 balance: 0 +// ====================== +// transfering 1. g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf +// transfering 100000 from: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p to: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// balance: 0 +// ====================== +// transfering 2. g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf +// transfering 100000 from: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p to: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// transfering 100000 from: g1vla5mffzum6060t99u4xhm8mnhgxr0sz4k574p to: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ====================== +// crossrealm caller : g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm balance: 0 +// unsaferealm caller: g1lpnflsxpr84dsqkznw85yd5wdzenkj89vsptmf balance: 10000000000 +// fake20 caller : g1zjzuzhywuxrk3cdvm7ma33vu4x2xyunxhzslu9 balance: 0