Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

launderrvictim.gno

10.23 Kb · 304 lines
  1// Package launderrvictim is the /r/-DATA-DECLARED variant of the
  2// launder-game victim. Its Immutable type is declared HERE (in /r/),
  3// not in /p/launderpkg. This is the recommended inter-realm pattern:
  4// realms declare their own logic data types.
  5//
  6// The hypothesis under test: with /r/-declared logic data, the
  7// Attack H/I/J/K/L laundering shapes are structurally impossible.
  8// Tests against this victim should all fail to mutate gImm.
  9package launderrvictim
 10
 11import "gno.land/p/demo/tests/launderpkg"
 12
 13// Immutable is /r/-declared (the key difference from /r/laundervictim,
 14// which uses /p/launderpkg.Immutable).
 15type Immutable struct {
 16	Field string
 17}
 18
 19// Read is /r/launderrvictim-declared, so calling it borrow rule #1 borrows
 20// m.Realm to launderrvictim.
 21func (i *Immutable) Read() string { return i.Field }
 22
 23var gImm *Immutable
 24
 25func init() {
 26	gImm = &Immutable{Field: "rdata-original"}
 27}
 28
 29// GetImm hands out a pointer to gImm. Standard "victim exposes a
 30// pointer to its state" antipattern — but with /r/-declared data,
 31// the attacker should still be unable to write through it.
 32func GetImm() *Immutable { return gImm }
 33
 34// ReadImm reads the current field for after-attack verification.
 35func ReadImm() string { return gImm.Field }
 36
 37// UseAnyMutator boxes gImm as any and dispatches a /p/-declared
 38// AnyMutator. This is the dangerous shape from Attack L: victim
 39// boxes its own /r/-declared data through a /p/-defined interface
 40// that the attacker can implement.
 41func UseAnyMutator(m launderpkg.AnyMutator) {
 42	m.Run(gImm)
 43}
 44
 45// ApplyHook dispatches a caller-supplied callback on gImm. The
 46// callback's parameter type is /r/launderrvictim-declared, so /p/
 47// packages can't supply this hook — only /r/ realms can.
 48func ApplyHook(h func(*Immutable)) {
 49	h(gImm)
 50}
 51
 52// --- /p/-type embedded / fielded inside /r/-declared types ---
 53//
 54// The three shapes below mix /r/-declared containers with /p/-typed
 55// inner state. Even though the container types are /r/-declared,
 56// the inner /p/-typed values inherit /p/'s methods — including
 57// Apply-style higher-order methods that take /p/-typed callbacks.
 58// /p/-attacker code can supply such callbacks. The attacker reaches
 59// the inner /p/-value (read access works), then invokes Apply with
 60// a /p/-declared function pointer. Inside Apply (borrow rule #2ed
 61// to /r/launderrvictim), the callback runs without any borrow rule #1 or #2
 62// shift (top-level /p/ fn), so the write commits under victim
 63// authority.
 64
 65// WithEmbed embeds launderpkg.Immutable by VALUE (method promotion
 66// gives WithEmbed an .Apply method).
 67type WithEmbed struct {
 68	launderpkg.Immutable
 69}
 70
 71// WithPtr has a POINTER FIELD to launderpkg.Immutable.
 72type WithPtr struct {
 73	Inner *launderpkg.Immutable
 74}
 75
 76// WithVal has a VALUE FIELD of launderpkg.Immutable (not embedded;
 77// the field is named, no method promotion — but the value is still
 78// addressable through c.Inner).
 79type WithVal struct {
 80	Inner launderpkg.Immutable
 81}
 82
 83var (
 84	gWithEmbed *WithEmbed
 85	gWithPtr   *WithPtr
 86	gWithVal   *WithVal
 87)
 88
 89func init() {
 90	gWithEmbed = &WithEmbed{Immutable: launderpkg.Immutable{Field: "embed-orig"}}
 91	gWithPtr = &WithPtr{Inner: &launderpkg.Immutable{Field: "ptr-orig"}}
 92	gWithVal = &WithVal{Inner: launderpkg.Immutable{Field: "val-orig"}}
 93}
 94
 95func GetWithEmbed() *WithEmbed { return gWithEmbed }
 96func GetWithPtr() *WithPtr     { return gWithPtr }
 97func GetWithVal() *WithVal     { return gWithVal }
 98
 99func ReadEmbed() string { return gWithEmbed.Field }
100func ReadPtr() string   { return gWithPtr.Inner.Field }
101func ReadVal() string   { return gWithVal.Inner.Field }
102
103// --- Methods-less /p/-type inner state ---
104// launderpkg.Bare has no methods. These containers wrap Bare in
105// the three field shapes. The question: does readonly taint catch
106// a direct field write through the /r/-container's getter?
107
108type WithBareEmbed struct {
109	launderpkg.Bare
110}
111
112type WithBarePtr struct {
113	Inner *launderpkg.Bare
114}
115
116type WithBareVal struct {
117	Inner launderpkg.Bare
118}
119
120var (
121	gWithBareEmbed *WithBareEmbed
122	gWithBarePtr   *WithBarePtr
123	gWithBareVal   *WithBareVal
124)
125
126func init() {
127	gWithBareEmbed = &WithBareEmbed{Bare: launderpkg.Bare{Field: "bare-embed-orig"}}
128	gWithBarePtr = &WithBarePtr{Inner: &launderpkg.Bare{Field: "bare-ptr-orig"}}
129	gWithBareVal = &WithBareVal{Inner: launderpkg.Bare{Field: "bare-val-orig"}}
130}
131
132func GetWithBareEmbed() *WithBareEmbed { return gWithBareEmbed }
133func GetWithBarePtr() *WithBarePtr     { return gWithBarePtr }
134func GetWithBareVal() *WithBareVal     { return gWithBareVal }
135
136func ReadBareEmbed() string { return gWithBareEmbed.Field }
137func ReadBarePtr() string   { return gWithBarePtr.Inner.Field }
138func ReadBareVal() string   { return gWithBareVal.Inner.Field }
139
140// --- Composite containers holding /p/-typed elements ---
141// Slices, arrays, maps of methods-less /p/-Bare values and pointers.
142
143var (
144	gBareSlice    []launderpkg.Bare
145	gBarePtrSlice []*launderpkg.Bare
146	gBareArr      [3]launderpkg.Bare
147	gBarePtrArr   [3]*launderpkg.Bare
148	gBareMap      map[string]launderpkg.Bare
149	gBarePtrMap   map[string]*launderpkg.Bare
150)
151
152func init() {
153	gBareSlice = []launderpkg.Bare{
154		{Field: "slice0"}, {Field: "slice1"},
155	}
156	gBarePtrSlice = []*launderpkg.Bare{
157		{Field: "ptrslice0"}, {Field: "ptrslice1"},
158	}
159	gBareArr = [3]launderpkg.Bare{
160		{Field: "arr0"}, {Field: "arr1"}, {Field: "arr2"},
161	}
162	gBarePtrArr = [3]*launderpkg.Bare{
163		{Field: "ptrarr0"}, {Field: "ptrarr1"}, {Field: "ptrarr2"},
164	}
165	gBareMap = map[string]launderpkg.Bare{
166		"a": {Field: "mapA"}, "b": {Field: "mapB"},
167	}
168	gBarePtrMap = map[string]*launderpkg.Bare{
169		"a": {Field: "ptrmapA"}, "b": {Field: "ptrmapB"},
170	}
171}
172
173func GetBareSlice() []launderpkg.Bare            { return gBareSlice }
174func GetBarePtrSlice() []*launderpkg.Bare        { return gBarePtrSlice }
175func GetBareArr() *[3]launderpkg.Bare            { return &gBareArr }
176func GetBarePtrArr() *[3]*launderpkg.Bare        { return &gBarePtrArr }
177func GetBareMap() map[string]launderpkg.Bare     { return gBareMap }
178func GetBarePtrMap() map[string]*launderpkg.Bare { return gBarePtrMap }
179
180func ReadBareSlice0() string      { return gBareSlice[0].Field }
181func ReadBareSlice0Then1() string { return gBareSlice[1].Field }
182func ReadBarePtrSlice0() string   { return gBarePtrSlice[0].Field }
183func ReadBareArr0() string        { return gBareArr[0].Field }
184func ReadBarePtrArr0() string     { return gBarePtrArr[0].Field }
185func ReadBareMapA() string        { return gBareMap["a"].Field }
186func ReadBarePtrMapA() string     { return gBarePtrMap["a"].Field }
187
188// --- Panic/defer/recover helpers ---
189// These victim-side helpers expose scenarios where the m.Realm
190// borrow can interact with deferred calls, recover(), and panics
191// in unusual control-flow shapes.
192
193// DeferCallback installs h as a defer inside an /r/launderrvictim
194// frame, then returns. h runs at frame pop. The question: at the
195// time h is invoked, m.Realm has just been restored to caller's
196// realm by PopFrameAndReturn — but wait, defers run BEFORE
197// PopFrameAndReturn. So m.Realm should still be victim's. Does
198// the deferred h then run under victim authority?
199func DeferCallback(h func(*Immutable)) {
200	defer h(gImm)
201}
202
203// PanicAfterPushDefer pushes a defer and then panics, so the defer
204// runs as part of panic unwinding. Tests that m.Realm is correctly
205// borrowed when the defer body invokes a foreign function.
206func PanicAfterPushDefer(h func(*Immutable)) {
207	defer h(gImm)
208	panic("victim-induced panic")
209}
210
211// DeferApplyHook defers an ApplyHook call. The deferred ApplyHook
212// itself runs borrow rule #1 to /r/launderrvictim, and inside the
213// callback runs as borrow rule #1 of the attacker's realm — the standard
214// known-open Apply pattern, but now triggered via defer.
215func DeferApplyHook(h func(*Immutable)) {
216	defer ApplyHook(h)
217}
218
219// RecoverAndRetry: inside a victim method, defer a recover, write
220// something to gImm, then panic. After the recover, the function
221// returns normally. Tests that internal panic/recover doesn't leak
222// state.
223func RecoverAndRetry(h func(*Immutable)) (recovered any) {
224	defer func() {
225		recovered = recover()
226	}()
227	h(gImm)
228	return
229}
230
231// CallThenPanic invokes h synchronously and then panics. If h is
232// attacker-supplied and writes via captured pointer, this is just
233// a re-shape of ApplyHook.
234func CallThenPanic(h func(*Immutable)) {
235	h(gImm)
236	panic("victim panic after callback")
237}
238
239// CallPDeferApply: multi-level defer chain. Victim invokes a
240// /p/-method (DeferApply) on a victim-owned *launderpkg.Immutable;
241// the /p/-method defers the attacker callback. Three frames at
242// callback time: attacker.main → victim.CallPDeferApply →
243// /p/.DeferApply (deferred fn dispatches here).
244func CallPDeferApply(fn func(*launderpkg.Immutable)) {
245	gWithPtr.Inner.DeferApply(fn)
246}
247
248// --- Stored-hook plumbing ---
249//
250// Victim accepts caller-registered callbacks and dispatches them
251// LATER, from inside a /r/-victim method body. If the registered
252// callback is /p/-declared and writes through a captured /r/-stamped
253// pointer, the laundering shape is: stored callback rather than
254// callback-arg.
255
256type ImmHook func(*Immutable)
257
258var gHooks []ImmHook
259
260func RegisterHook(h ImmHook) { gHooks = append(gHooks, h) }
261func RunHooks() {
262	for _, h := range gHooks {
263		h(gImm)
264	}
265}
266
267type PlainHook func()
268
269var gPlainHooks []PlainHook
270
271func RegisterPlainHook(h PlainHook) { gPlainHooks = append(gPlainHooks, h) }
272func RunPlainHooks() {
273	for _, h := range gPlainHooks {
274		h()
275	}
276}
277
278func ClearHooks() {
279	gHooks = nil
280	gPlainHooks = nil
281}
282
283// MakeWriterClosure constructs a /r/-victim-declared closure that
284// captures gImm and writes through it. The closure body is /r/-victim-
285// declared, so borrow rule #1 fires at invocation → m.Realm = /r/-victim →
286// write commits with victim authority. Returning this closure to an
287// attacker is "consenting to write" by the victim.
288func MakeWriterClosure(value string) func() {
289	return func() {
290		gImm.Field = value
291	}
292}
293
294// MakeApplyTrampoline returns a closure that captures &gImm.Field
295// indirectly: it captures *Immutable, and dispatches a caller-supplied
296// callback fn on it. /r/-victim-declared body → borrow rule #1 → m.Realm =
297// /r/-victim. If `fn` is /p/-declared (e.g. EvilWrite), it inherits
298// victim authority. This is "victim returns a closure that's itself
299// an Apply-style trampoline" — a packaged Apply.
300func MakeApplyTrampoline() func(func(*Immutable)) {
301	return func(fn func(*Immutable)) {
302		fn(gImm)
303	}
304}