launderpkg.gno
5.56 Kb · 151 lines
1// Package launderpkg defines a struct type that the laundervictim
2// realm uses as the type of its package-level state. The Set method
3// is the "innocent /p/-helper" that an attacker tries to weaponize
4// via the receiver-borrow rule (PushFrameCall borrow rule 2):
5// when the receiver is owned by /r/X, calling Set borrows m.Realm to
6// /r/X, and the write inside Set runs with /r/X authority.
7package launderpkg
8
9type Object struct {
10 Field string
11}
12
13// PInitData is a /p/-init-allocated package-level data var. Its
14// StructValue carries ObjectInfo.PkgID = /p/demo/tests/launderpkg.
15// Used by zrealm_launder_pdata_* filetests to probe the /p/-source
16// read patterns that the existing 62 /r/-source launder filetests
17// don't cover. Empirically, direct value-read and pointer-deref of
18// PInitData from a /r/-caller both panic readonly tainted — the
19// /p/-source bytes are not adoptable into /r/-authority via these
20// patterns.
21var PInitData = Object{Field: "p-init"}
22
23// Set is the canonical "/p/ helper that mutates /r/-owned state"
24// pattern. Looks benign — like list.Set, avl.Set, etc. — but if a
25// foreign caller obtains a pointer to a victim realm's Object, they
26// can invoke Set on it and the borrow rule grants them victim
27// authority for the call. Exposing a *Object out of a realm is
28// equivalent to consenting to mutation by any caller that holds the
29// pointer.
30func (o *Object) Set(s string) {
31 o.Field = s
32}
33
34// Read is a read-only accessor.
35func (o *Object) Read() string {
36 return o.Field
37}
38
39// Mutator is an interface — callers can pass any implementation. A
40// realistic pattern: /p/orig defines a hook interface and exposes a
41// method that lets callers register an impl for some operation.
42type Mutator interface {
43 Run(*Immutable)
44}
45
46// UseMutator is a /p/-method on *Object that dispatches an interface
47// method (Mutator.Run) with target as the argument. This is the
48// realistic shape — a /p/-library invoking a user-supplied hook on
49// /r/-owned data.
50func (o *Object) UseMutator(target *Immutable, m Mutator) {
51 m.Run(target)
52}
53
54// SliceMutator's Run takes a SLICE of pointers — exercises the
55// "non-pointer parameter that still conveys writable foreign data"
56// shape that Attack K explores.
57type SliceMutator interface {
58 Run([]*Immutable)
59}
60
61// UseSliceMutator dispatches a SliceMutator on a single-element
62// slice carrying target.
63func (o *Object) UseSliceMutator(target *Immutable, m SliceMutator) {
64 m.Run([]*Immutable{target})
65}
66
67// AnyMutator's Run takes `any` — interface-typed parameter. The
68// caller can box a *Immutable into the interface and an attacker
69// impl can type-assert back to write. Tests whether the predicate
70// needs to treat interface-typed params as potentially foreign.
71type AnyMutator interface {
72 Run(any)
73}
74
75// UseAnyMutator boxes target into `any` before dispatching.
76func (o *Object) UseAnyMutator(target *Immutable, m AnyMutator) {
77 m.Run(target)
78}
79
80// Bare is a /p/-declared struct type with NO methods. Used to test
81// whether embedding/fielding a methods-less /p/-type inside an
82// /r/-declared container exposes any laundering vector through
83// DIRECT field writes (no method dispatch, no Apply callback). The
84// expectation: readonly taint on the /r/-container propagates to
85// inner /p/-typed fields and direct writes panic.
86type Bare struct {
87 Field string
88}
89
90// Immutable is a deliberately read-only /p/ type: same struct layout
91// as Object, but no mutator method. A realm using Immutable as the
92// type of an exposed field intends "read-only API." The launder game
93// variation explores whether an attacker /p/ package can convert
94// *Immutable to a type with a mutator method declared elsewhere.
95type Immutable struct {
96 Field string
97}
98
99// Read is a read-only accessor.
100func (i *Immutable) Read() string {
101 return i.Field
102}
103
104// Apply is a higher-order helper that hands the *Immutable to a
105// caller-supplied callback. The signature looks read-only (no
106// mutator method on *Immutable itself), but Apply hands out an
107// addressable pointer to victim-owned memory while m.Realm is
108// borrowed to the victim. A /p/-declared callback substituted by
109// the caller therefore runs with victim authority. This is the
110// avl.Tree.Iterate / list.ForEach shape — common in /p/ libraries.
111func (i *Immutable) Apply(fn func(*Immutable)) {
112 fn(i)
113}
114
115// BumpToPwn is a no-arg, no-return /p/-method that mutates the
116// receiver. Used by stored-bound-method-value laundering probes:
117// `mv := victimImmPtr.BumpToPwn` has type `func()`, which fits
118// PlainHook = func(). When the bound method value is stored and
119// invoked later from /r/-victim context, recv-borrow borrow rule #2 fires on
120// the /r/-victim-stamped recv → m.Realm = /r/-victim → write
121// commits.
122func (i *Immutable) BumpToPwn() {
123 i.Field = "pwnd-via-bound-mv"
124}
125
126// DeferApply is the defer-variant of Apply: schedules fn(i) as a
127// defer instead of calling synchronously. Used to probe whether
128// the borrow rules apply the same when a callback is invoked from
129// inside a /p/-function's defer queue.
130func (i *Immutable) DeferApply(fn func(*Immutable)) {
131 defer fn(i)
132}
133
134// PanicAfterApply: invokes fn(i) synchronously, then panics after
135// return. If fn ran without panicking (write succeeded), the panic
136// here is /p/-realm panic propagating up.
137func (i *Immutable) PanicAfterApply(fn func(*Immutable)) {
138 fn(i)
139 panic("post-apply panic")
140}
141
142// RecoverApply: defers a recover(), then calls fn(i). If fn panics
143// with readonly, recover catches it (within the same /p/-pkg
144// frame). Returns the recovered value.
145func (i *Immutable) RecoverApply(fn func(*Immutable)) (rec any) {
146 defer func() {
147 rec = recover()
148 }()
149 fn(i)
150 return
151}