code wiki / _hdl_build / nx_suite_share_gate.nx
nx_suite_share_gate.nx source
↩ module page · 212 lines · 10168 B
1// nx_suite_share_gate.nx -- the retirement proof. nx_share_plane's 19 teeth were the reason to trust it;
2// if the facade cannot carry the SAME attacks on the canonical plane, the retirement is a regression
3// dressed up as cleanup. So these are deliberately the same attacks, re-aimed at nx_suite_share, plus the
4// two teeth for the divergences the convergence proof said had to survive the move.
5// license_tier: ORIGINAL expect_exit: 0
6import "nx_suite_share.nx"
7
8const SS_ALICE: i64 = 1001
9const SS_BOB: i64 = 1002
10const SS_CARA: i64 = 1003
11const SS_DAN: i64 = 1004
12const SS_ALBUM: i64 = 7001
13const SS_CARD: i64 = 7002
14const SS_MAXOUT: i64 = 64
15const SG_ALBUM_K: *u8 = "album\x00"
16const SG_CONTACT_K: *u8 = "contact\x00"
17
18func s_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
19func s_n(v: i64) -> i64 {
20 let bb: *u8=sys_mmap(28); var m: i64=v
21 if m<0 { m=0-m; sys_write(1,"-" as *u8,1) }
22 let t: *u8=sys_mmap(28); var k: i64=0
23 if m==0 { t[0]=48 as u8; k=1 }
24 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
25 var i: i64=0
26 while i<k { bb[i]=t[k-1-i]; i=i+1 }
27 sys_write(1,bb,k); return 0
28}
29func s_t(pass: i64, label: *u8, fails: *i64) -> i64 {
30 s_w(" " as *u8); s_w(label); s_w(": " as *u8)
31 if pass==1 { s_w("PASS\n" as *u8) } else { s_w("FAIL\n" as *u8); fails[0]=fails[0]+1 }
32 return 0
33}
34func px(tag: i64) -> *u8 {
35 let p: *u8 = sys_mmap(128); var o: i64 = 0
36 o = rb_cat(p, o, "/tmp/sgate_" as *u8); o = rb_catn(p, o, sys_now_us())
37 o = rb_cat(p, o, "_" as *u8); o = rb_catn(p, o, tag); o = rb_cat(p, o, "_" as *u8)
38 p[o] = 0 as u8
39 return p
40}
41
42func main() -> i64 {
43 let fails: *i64 = sys_mmap(16) as *i64
44 fails[0]=0
45 s_w("=== nx_suite_share_gate -- the retired plane's attacks, re-aimed at the canonical one ===\n" as *u8)
46
47 // T1 deny by default on an unclaimed album
48 let p1: *u8 = px(1)
49 var t1: i64=0
50 if sg_may(p1, SS_ALICE, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0 {
51 if sg_may(p1, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0 { t1=1 }
52 }
53 s_t(t1, "T1 DENY BY DEFAULT nothing is visible on an unclaimed album" as *u8, fails)
54
55 // T2 the owner sees their own with no grant tuple in existence
56 let p2: *u8 = px(2)
57 sg_claim(p2, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
58 var t2: i64=0
59 if sg_may(p2, SS_ALICE, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1 {
60 if sg_may(p2, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0 { t2=1 }
61 }
62 s_t(t2, "T2 the owner sees their own album and a stranger still does not" as *u8, fails)
63
64 // T3 a viewer grant grants viewing and not curating
65 let p3: *u8 = px(3)
66 sg_claim(p3, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
67 sg_grant(p3, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
68 var t3: i64=0
69 if sg_may(p3, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1 {
70 if sg_may(p3, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_ADD)==0 { t3=1 }
71 }
72 s_t(t3, "T3 a viewer grant grants viewing and NOT adding" as *u8, fails)
73
74 // T4 ATTACK a viewer re-shares
75 let p4: *u8 = px(4)
76 sg_claim(p4, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
77 sg_grant(p4, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
78 let esc: i64 = sg_grant(p4, SS_BOB, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
79 var t4: i64=0
80 if esc==SG_E_NOTOWNER { if sg_may(p4, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0 { t4=1 } }
81 s_t(t4, "T4 ATTACK a viewer cannot re-share; no transitive escalation" as *u8, fails)
82
83 // T5 MIGRATED DIVERGENCE 2: delegated re-share now rides the `curator` relation, i.e. a rewrite-table
84 // row on the shared plane. If this fails, retiring nx_share_plane DROPPED a capability instead of
85 // moving it, which is the failure mode a retirement is most likely to hide.
86 let p5: *u8 = px(5)
87 sg_claim(p5, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
88 sg_grant(p5, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_CURATOR)
89 let ok5: i64 = sg_grant(p5, SS_BOB, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
90 var t5: i64=0
91 if ok5==1 { if sg_may(p5, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1 { t5=1 } }
92 s_t(t5, "T5 MIGRATED a curator CAN delegate re-sharing (divergence 2 carried across as data)" as *u8, fails)
93
94 // T6 revocation is total, and the record still grew
95 let p6: *u8 = px(6)
96 sg_claim(p6, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
97 sg_grant(p6, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
98 sg_revoke(p6, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
99 s_t(sg_may(p6, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0,
100 "T6 revoke is total and immediate" as *u8, fails)
101
102 // T7 grant-revoke-grant resolves to the last write
103 let p7: *u8 = px(7)
104 sg_claim(p7, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
105 sg_grant(p7, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
106 sg_revoke(p7, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
107 sg_grant(p7, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
108 s_t(sg_may(p7, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1,
109 "T7 grant-revoke-grant resolves to the LAST thing that happened" as *u8, fails)
110
111 // T8 self-grant refused, owner keeps their own
112 let p8: *u8 = px(8)
113 sg_claim(p8, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
114 let slf: i64 = sg_grant(p8, SS_ALICE, SS_ALICE, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
115 var t8: i64=0
116 if slf==SG_E_SELF { if sg_may(p8, SS_ALICE, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1 { t8=1 } }
117 s_t(t8, "T8 a self-grant is REFUSED and the owner still sees their own" as *u8, fails)
118
119 // T9 claim idempotent for the owner, refused for a thief
120 let p9: *u8 = px(9)
121 sg_claim(p9, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
122 let again: i64 = sg_claim(p9, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
123 let steal: i64 = sg_claim(p9, SS_BOB, SG_ALBUM_K, SS_ALBUM)
124 var t9: i64=0
125 if again==0 { if steal==SG_E_NOTOWNER { t9=1 } }
126 s_t(t9, "T9 claim is idempotent for the owner and REFUSED for anyone else" as *u8, fails)
127
128 // T10 KEPT DIVERGENCE 1: sharing an UNCLAIMED album is refused, even though the plane beneath would
129 // have allowed a bootstrap first-grant. The facade tightens; it must never loosen.
130 let p10: *u8 = px(10)
131 let unc: i64 = sg_grant(p10, SS_BOB, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
132 var t10: i64=0
133 if unc==SG_E_UNCLAIMED { if sg_may(p10, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==0 { t10=1 } }
134 s_t(t10, "T10 KEPT sharing an UNCLAIMED album is refused (divergence 1 held, facade tightens not loosens)" as *u8, fails)
135
136 // T11 a gift MOVES ownership -- the giver loses it. The tooth that catches a gift built as a copy.
137 let p11: *u8 = px(11)
138 sg_claim(p11, SS_ALICE, SG_ALBUM_K, SS_CARD)
139 let g: i64 = sg_gift(p11, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_CARD)
140 var t11: i64=0
141 if g==1 {
142 if sg_may(p11, SS_BOB, SG_ALBUM_K, SS_CARD, SG_VIEW)==1 {
143 if sg_may(p11, SS_ALICE, SG_ALBUM_K, SS_CARD, SG_VIEW)==0 { t11=1 }
144 }
145 }
146 s_t(t11, "T11 a gift TRANSFERS ownership: receiver gains it, giver LOSES it" as *u8, fails)
147
148 // T12 ATTACK a former owner re-gifts what they already gave away
149 let p12: *u8 = px(12)
150 sg_claim(p12, SS_ALICE, SG_ALBUM_K, SS_CARD)
151 sg_gift(p12, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_CARD)
152 let re: i64 = sg_gift(p12, SS_ALICE, SS_CARA, SG_ALBUM_K, SS_CARD)
153 s_t(re==SG_E_NOTOWNER, "T12 ATTACK a former owner cannot re-gift what they gave away" as *u8, fails)
154
155 // T13 provenance comes out of the append-only log, with no second ledger kept anywhere
156 let p13: *u8 = px(13)
157 sg_claim(p13, SS_ALICE, SG_ALBUM_K, SS_CARD)
158 sg_gift(p13, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_CARD)
159 sg_gift(p13, SS_BOB, SS_CARA, SG_ALBUM_K, SS_CARD)
160 let chain: *i64 = sys_mmap(8*SS_MAXOUT) as *i64
161 let cn: i64 = sg_provenance(p13, SG_ALBUM_K, SS_CARD, chain, SS_MAXOUT)
162 s_w(" provenance hops=" as *u8); s_n(cn); s_w(" chain:" as *u8)
163 var i: i64=0
164 while i<cn { s_w(" " as *u8); s_w(chain[i] as *u8); i=i+1 }
165 s_w("\n" as *u8)
166 var t13: i64=0
167 if cn==3 {
168 if rb_seq(chain[0] as *u8, "user:1001" as *u8)==1 {
169 if rb_seq(chain[1] as *u8, "user:1002" as *u8)==1 {
170 if rb_seq(chain[2] as *u8, "user:1003" as *u8)==1 { t13=1 }
171 }
172 }
173 }
174 s_t(t13, "T13 the gift chain alice->bob->cara is recoverable from the log alone" as *u8, fails)
175
176 // T14 class isolation now comes from the object namespace itself
177 let p14: *u8 = px(14)
178 sg_claim(p14, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
179 sg_claim(p14, SS_ALICE, SG_CONTACT_K, SS_ALBUM)
180 sg_grant(p14, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
181 var t14: i64=0
182 if sg_may(p14, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_VIEW)==1 {
183 if sg_may(p14, SS_BOB, SG_CONTACT_K, SS_ALBUM, SG_VIEW)==0 { t14=1 }
184 }
185 s_t(t14, "T14 the same id in another class is another object (album grant does not reach the contact card)" as *u8, fails)
186
187 // T15 the who-can-see-this panel drops a revoked person
188 let p15: *u8 = px(15)
189 sg_claim(p15, SS_ALICE, SG_ALBUM_K, SS_ALBUM)
190 sg_grant(p15, SS_ALICE, SS_BOB, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
191 sg_grant(p15, SS_ALICE, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
192 sg_grant(p15, SS_ALICE, SS_DAN, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
193 sg_revoke(p15, SS_ALICE, SS_CARA, SG_ALBUM_K, SS_ALBUM, SG_R_VIEWER)
194 let vw: *i64 = sys_mmap(8*SS_MAXOUT) as *i64
195 let vn: i64 = sg_viewers(p15, SG_ALBUM_K, SS_ALBUM, vw, SS_MAXOUT)
196 var has_cara: i64 = 0
197 i=0
198 while i<vn { if rb_seq(vw[i] as *u8, "user:1003" as *u8)==1 { has_cara=1 } i=i+1 }
199 s_w(" live viewers=" as *u8); s_n(vn); s_w("\n" as *u8)
200 var t15: i64=0
201 if vn==2 { if has_cara==0 { t15=1 } }
202 s_t(t15, "T15 the viewer panel is recomputed from the log so a revoked person vanishes" as *u8, fails)
203
204 s_w(" fails=" as *u8); s_n(fails[0]); s_w("\n" as *u8)
205 if fails[0]==0 {
206 s_w("VERDICT: verdict=GREEN (15/15 -- every attack the retired plane blocked is still blocked, on ONE plane)\n" as *u8)
207 sys_exit(0)
208 }
209 s_w("VERDICT: RED (the retirement dropped something the old plane protected)\n" as *u8)
210 sys_exit(1)
211 return 1
212}