code wiki / _hdl_build / nx_deploy_stage_candidate_t188.nx
nx_deploy_stage_candidate_t188.nx source
↩ module page · 321 lines · 14827 B
1// ELF-magic admission and exact-byte staging into a .new slot.
2// This preserves ordinary source permissions and verifies copied bytes; it does
3// not establish that an ELF is valid for execution. Legacy1024-byte admission
4// remains explicit pending format-aware admission; no64MiB content ceiling.
5// Caller owns trusted stage directories. File and directory sync outcomes are
6// recorded separately from visibility. Promotion and runtime health are separate.
7// license_tier: ORIGINAL
8import "nx_fio.nx"
9import "nx_descriptor_candidate_t188.nx"
10const DS_MAGIC_1024: i64 = 1024
11const DS_MAGIC_4096: i64 = 4096
12const DS_MAGIC_2000: i64 = 2000
13
14const DS_MINSZ: i64 = 1024 // smallest plausible ELF; a smaller "binary" is a failed/torn build
15
16func ds_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
17func ds_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
18func ds_cat(d: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; var o: i64=off; while s[i]!=(0 as u8){d[o]=s[i]; o=o+1; i=i+1} return o }
19func ds_catn(d: *u8, off: i64, v: i64) -> i64 {
20 let t: *u8 = sys_mmap(28); var m: i64=v; var o: i64=off; if m<0 { d[o]=45 as u8; o=o+1; m=0-m }
21 var k: i64=0; if m==0 { t[0]=48 as u8; k=1 } while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
22 var i: i64=0; while i<k { d[o]=t[k-1-i]; o=o+1; i=i+1 } return o
23}
24
25// dst path must end with ".new" -> refuses any live-binary target. 1=ok, 0=refused.
26func ds_dst_is_new(dst: *u8) -> i64 {
27 let n: i64 = ds_len(dst)
28 if n < 4 { return 0 }
29 if dst[n-4] != (46 as u8) { return 0 } // .
30 if dst[n-3] != (110 as u8) { return 0 } // n
31 if dst[n-2] != (101 as u8) { return 0 } // e
32 if dst[n-1] != (119 as u8) { return 0 } // w
33 return 1
34}
35
36// verify src is a nonzero ELF; returns size, or negative rc. sets magic ok.
37// Staging receipt distinguishes a visible rename from confirmed directory sync.
38struct NxDeployStageResult {
39 code:i64, system_code:i64, visible:i64, durable:i64, bytes:i64, mode:i64, block:i64, cleanup_code:i64, temp_owned:i64, temp_dev:i64, temp_ino:i64, stage:*u8,
40}
41func ds_stat_word(stat:*u8,offset:i64)->i64{
42 return *((stat+offset) as *i64)
43}
44func ds_stage_result_init(r:*NxDeployStageResult)->i64{
45 r.code=0;r.system_code=0;r.visible=0;r.durable=0;r.bytes=0;r.mode=0;r.block=0;r.cleanup_code=0;r.temp_owned=0;r.temp_dev=0;r.temp_ino=0;r.stage="input";return 0
46}
47func ds_source_prepare(src:*u8,region:*NxFileReadRegion,stat:*u8,digest:*u8,r:*NxDeployStageResult)->i64{
48 var block:i64=0
49 r.stage="source-open"
50 if fio_region_open(src,region)!=0 {r.code=0-3;r.system_code=region.code}
51 if r.code==0 {
52 r.stage="source-metadata";r.system_code=sys_fstat_fd(region.fd,stat)
53 if r.system_code<0 {r.code=0-13}
54 }
55 if r.code==0 {
56 let fullmode:i64=(stat[NX_STAT_X64_MODE_OFFSET] as i64)+((stat[NX_STAT_X64_MODE_OFFSET+1] as i64)<<8)
57 if (fullmode & NX_STAT_TYPE_MASK)!=NX_STAT_REGULAR_FILE {r.code=0-4;r.stage="source-not-regular"}
58 r.mode=sys_stat_permissions(stat)
59 r.bytes=region.total
60 if ds_stat_word(stat,NX_STAT_X64_SIZE_OFFSET)!=r.bytes {r.code=0-13;r.stage="source-extent-changed"}
61 if r.bytes<DS_MINSZ {r.code=0-6;r.stage="source-too-small"}
62 // SHA-256 bit-count representation, not an artifact-size admission policy.
63 if r.bytes>9223372036854775807/8 {r.code=0-7;r.stage="digest-length-overflow"}
64 block=ds_stat_word(stat,NX_STAT_X64_BLOCK_OFFSET)
65 if block<=0 {r.code=0-13;r.stage="filesystem-block-size"}
66 if block>r.bytes {block=r.bytes}
67 }
68 if r.code==0 {
69 r.stage="source-elf"
70 var hn:i64=0
71 while hn<4 {
72 let n:i64=sys_read(region.fd,digest+hn,4-hn)
73 if n==FIO_EINTR{continue}
74 if n<=0{r.code=0-4;r.system_code=n;break};hn=hn+n
75 }
76 if r.code==0 && (digest[0]!=(127 as u8)||digest[1]!=(69 as u8)||digest[2]!=(76 as u8)||digest[3]!=(70 as u8)){r.code=0-4}
77 if r.code==0 && sys_lseek(region.fd,0,0)!=0{r.code=0-9;r.stage="source-rewind"}
78 }
79
80 r.block=block;return r.code
81}
82func ds_src_size(src:*u8)->i64{
83 let needed:i64=__size_of(NxFileReadRegion)+__size_of(NxDeployStageResult)+NX_STAT_X64_BYTES+4
84 let mem:*u8=sys_mmap_try(needed);if (mem as i64)<=0{return 0-12}
85 let region:*NxFileReadRegion=mem as *NxFileReadRegion
86 let r:*NxDeployStageResult=(mem+__size_of(NxFileReadRegion)) as *NxDeployStageResult
87 let stat:*u8=(r as *u8)+__size_of(NxDeployStageResult)
88 fio_region_init(region);ds_stage_result_init(r)
89 ds_source_prepare(src,region,stat,stat+NX_STAT_X64_BYTES,r)
90 fio_region_close(region)
91 var result:i64=r.code
92 if result==0{if region.code!=0{result=0-9}else{result=r.bytes}}
93 sys_munmap_direct(mem,needed);return result
94}
95
96func ds_stage_core(src:*u8,dst:*u8,r:*NxDeployStageResult)->i64{
97 ds_stage_result_init(r)
98 if ds_dst_is_new(dst)==0 {r.code=0-2;return r.code}
99 let dn:i64=ds_len(dst)
100 if dn>9223372036854775807-7 {r.code=0-12;r.stage="path-overflow";return r.code}
101 let fixed_n:i64=__size_of(NxFileReadRegion)+__size_of(NxFileWriteResult)+NX_STAT_X64_BYTES+64+sha256_workspace_bytes()
102 let fixed:*u8=sys_mmap_try(fixed_n)
103 if (fixed as i64)<=0 {r.code=0-12;r.stage="workspace-allocation";return r.code}
104 let region:*NxFileReadRegion=fixed as *NxFileReadRegion
105 let sync:*NxFileWriteResult=(fixed+__size_of(NxFileReadRegion)) as *NxFileWriteResult
106 let stat:*u8=(sync as *u8)+__size_of(NxFileWriteResult)
107 let digest:*u8=stat+NX_STAT_X64_BYTES
108 let sha:*u8=digest+64
109 fio_region_init(region)
110 var paths:*u8=0 as *u8;var buffer:*u8=0 as *u8;var block:i64=0;var wf:i64=0-1
111 var tmp:*u8=0 as *u8;var parent:*u8=0 as *u8
112 ds_source_prepare(src,region,stat,digest,r)
113 block=r.block
114 if r.code==0 {
115 // Two complete path buffers, derived from admitted destination length.
116 if dn>9223372036854775807/2-7 {r.code=0-12;r.stage="path-overflow"}
117 else {
118 paths=sys_mmap_try((dn+7)*2);buffer=sys_mmap_try(block)
119 if (paths as i64)<=0 || (buffer as i64)<=0 {r.code=0-12;r.stage="transfer-allocation"}
120 }
121 }
122 if r.code==0 {
123 tmp=paths;parent=paths+dn+7
124 var p:i64=ds_cat(tmp,0,dst);p=ds_cat(tmp,p,".stg");tmp[p]=0 as u8
125 var slash:i64=0-1;var j:i64=0
126 while j<dn {if dst[j]==(47 as u8){slash=j};j=j+1}
127 if slash<0 {parent[0]=46 as u8;parent[1]=0 as u8}
128 else {var end:i64=slash;if end==0{end=1};j=0;while j<end{parent[j]=dst[j];j=j+1};parent[end]=0 as u8}
129 // Exclusive read/write temporary: never truncate a prior owner or follow a symlink.
130 r.stage="temporary-create"
131 wf=__syscall(SYS_OPENAT,AT_FDCWD,tmp,194|O_NOFOLLOW,MODE_0600,0,0)
132 if wf<0 {r.code=0-8;r.system_code=wf}
133 else {
134 r.temp_owned=1
135 r.system_code=sys_fstat_fd(wf,stat)
136 if r.system_code!=0 {r.code=0-13;r.stage="temporary-metadata"}
137 else {r.temp_dev=ds_stat_word(stat,NX_STAT_X64_DEVICE_OFFSET);r.temp_ino=ds_stat_word(stat,NX_STAT_X64_INODE_OFFSET)}
138 }
139 }
140 if r.code==0 {
141 sha256_init_workspace(sha,sha256_workspace_bytes())
142 while r.code==0 && region.read_bytes<region.length {
143 let first:i64=region.read_bytes
144 let got:i64=fio_region_next(region,buffer,block)
145 if got<=0 {r.code=0-9;r.system_code=got;r.stage="source-read-no-progress";break}
146 if first==0 {
147 if got<4 || buffer[0]!=(127 as u8) || buffer[1]!=(69 as u8) || buffer[2]!=(76 as u8) || buffer[3]!=(70 as u8) {
148 r.code=0-4;r.stage="source-not-elf";break
149 }
150 }
151 sha256_update(sha as *Sha256,buffer,got)
152 var sent:i64=0
153 while sent<got {
154 let w:i64=sys_write(wf,buffer+sent,got-sent)
155 if w==FIO_EINTR {continue}
156 if w<=0 {r.code=0-9;r.system_code=w;r.stage="temporary-write";break}
157 sent=sent+w
158 }
159 }
160 if r.code==0 {sha256_final(sha as *Sha256,digest)}
161 }
162 if r.code==0 {
163 r.stage="temporary-verify"
164 if sys_lseek(wf,0,2)!=r.bytes || sys_lseek(wf,0,0)!=0 {r.code=0-11}
165 else {
166 sha256_init_workspace(sha,sha256_workspace_bytes())
167 var got:i64=0
168 while got<r.bytes && r.code==0 {
169 var want:i64=r.bytes-got;if want>block{want=block}
170 let n:i64=sys_read(wf,buffer,want)
171 if n==FIO_EINTR{continue}
172 if n<=0 {r.code=0-11;r.system_code=n;break}
173 sha256_update(sha as *Sha256,buffer,n);got=got+n
174 }
175 if r.code==0 {
176 sha256_final(sha as *Sha256,digest+32)
177 var i:i64=0;while i<32 {if digest[i]!=digest[32+i]{r.code=0-11};i=i+1}
178 }
179 }
180 }
181 if r.code==0 {
182 r.stage="temporary-mode";r.system_code=sys_fchmod_fd(wf,r.mode)
183 if r.system_code!=0 {r.code=0-14}
184 }
185 if r.code==0 {
186 r.stage="temporary-sync"
187 r.system_code=fio_write_sync_fd(wf,buffer,0,sync);wf=0-1
188 if r.system_code!=0 {r.code=0-15}
189 }
190 if wf>=0 {let closed:i64=sys_close(wf);wf=0-1;if r.code==0 && closed!=0{r.code=0-15;r.system_code=closed}}
191 fio_region_close(region)
192 if r.code==0 && region.code!=0 {r.code=0-9;r.system_code=region.code;r.stage="source-close"}
193 if r.code==0 {
194 r.stage="publish-identity"
195 r.system_code=sys_fstatat(tmp,stat)
196 if r.system_code!=0 || ds_stat_word(stat,NX_STAT_X64_DEVICE_OFFSET)!=r.temp_dev || ds_stat_word(stat,NX_STAT_X64_INODE_OFFSET)!=r.temp_ino {r.code=0-17}
197 }
198 if r.code==0 {
199 r.stage="publish";r.system_code=sys_renameat(tmp,dst)
200 if r.system_code!=0 {r.code=0-10}else{r.visible=1;r.temp_owned=0}
201 }
202 if r.code==0 {
203 r.stage="directory-sync";let df:i64=sys_openat_directory(parent)
204 if df<0 {r.code=0-16;r.system_code=df}
205 else {r.system_code=fio_write_sync_fd(df,buffer,0,sync);if r.system_code!=0{r.code=0-16}else{r.durable=1;r.stage="staged"}}
206 }
207 // Only remove this invocation's temporary after matching its retained identity.
208 // Stage directories are trusted; this is not a hostile-directory race guarantee.
209 if r.code!=0 && r.visible==0 && r.temp_owned==1 {
210 r.cleanup_code=sys_fstatat(tmp,stat)
211 if r.cleanup_code==0 {
212 if ds_stat_word(stat,NX_STAT_X64_DEVICE_OFFSET)==r.temp_dev && ds_stat_word(stat,NX_STAT_X64_INODE_OFFSET)==r.temp_ino {
213 r.cleanup_code=fio_unlink(tmp);if r.cleanup_code==0{r.temp_owned=0}
214 } else {r.cleanup_code=0-17}
215 }
216 }
217 if (buffer as i64)>0 {sys_munmap_direct(buffer,block)}
218 if (paths as i64)>0 {sys_munmap_direct(paths,(dn+7)*2)}
219 sys_munmap_direct(fixed,fixed_n)
220 return r.code
221}
222func ds_stage(src:*u8,dst:*u8)->i64{
223 let r:*NxDeployStageResult=sys_mmap_try(__size_of(NxDeployStageResult)) as *NxDeployStageResult
224 if (r as i64)<=0{return 0-12}
225 let rc:i64=ds_stage_core(src,dst,r)
226 sys_munmap_direct(r as *u8,__size_of(NxDeployStageResult));return rc
227}
228
229
230func ds_out_fd(fd:i64,s:*u8,n:i64)->i64{
231 var done:i64=0
232 while done<n{let w:i64=sys_write(fd,s+done,n-done);if w==FIO_EINTR{continue};if w<=0{return 0-1};done=done+w};return 0
233}
234func ds_text_fd(fd:i64,s:*u8)->i64{return ds_out_fd(fd,s,ds_len(s))}
235func ds_json_fd(fd:i64,s:*u8)->i64{
236 if ds_text_fd(fd,"\"")!=0{return 0-1}
237 var i:i64=0;var begin:i64=0
238 let hex:*u8="0123456789abcdef"
239 while s[i]!=(0 as u8){
240 let ch:i64=s[i] as i64
241 if ch<32||ch==34||ch==92{
242 if ds_out_fd(fd,s+begin,i-begin)!=0{return 0-1}
243 if ch==34||ch==92{if ds_text_fd(fd,"\\")!=0||ds_out_fd(fd,s+i,1)!=0{return 0-1}}
244 else{if ds_text_fd(fd,"\\u00")!=0||ds_out_fd(fd,hex+(ch>>4),1)!=0||ds_out_fd(fd,hex+(ch&15),1)!=0{return 0-1}}
245 begin=i+1
246 }
247 i=i+1
248 }
249 if ds_out_fd(fd,s+begin,i-begin)!=0{return 0-1}
250 return ds_text_fd(fd,"\"")
251}
252// At most20 recursive digit frames for an i64, including its minimum value.
253func ds_digits_fd(fd:i64,v:i64)->i64{
254 if v<=0-10||v>=10{if ds_digits_fd(fd,v/10)!=0{return 0-1}}
255 var digit:i64=v%10;if digit<0{digit=0-digit}
256 return ds_out_fd(fd,"0123456789"+digit,1)
257}
258func ds_num_fd(fd:i64,v:i64)->i64{
259 if v<0{if ds_text_fd(fd,"-")!=0{return 0-1}};return ds_digits_fd(fd,v)
260}
261func ds_emit_result(fd:i64,src:*u8,dst:*u8,r:*NxDeployStageResult)->i64{
262 if ds_text_fd(fd,"{\"organ\":\"nx_deploy_stage\",\"src\":")!=0||ds_json_fd(fd,src)!=0{return 0-1}
263 if ds_text_fd(fd,",\"dst\":")!=0||ds_json_fd(fd,dst)!=0{return 0-1}
264 if ds_text_fd(fd,",\"rc\":")!=0||ds_num_fd(fd,r.code)!=0{return 0-1}
265 if ds_text_fd(fd,",\"bytes\":")!=0||ds_num_fd(fd,r.bytes)!=0{return 0-1}
266 if ds_text_fd(fd,",\"mode\":")!=0||ds_num_fd(fd,r.mode)!=0{return 0-1}
267 if ds_text_fd(fd,",\"system_code\":")!=0||ds_num_fd(fd,r.system_code)!=0{return 0-1}
268 if ds_text_fd(fd,",\"visible\":")!=0||ds_num_fd(fd,r.visible)!=0{return 0-1}
269 if ds_text_fd(fd,",\"durable\":")!=0||ds_num_fd(fd,r.durable)!=0{return 0-1}
270 if ds_text_fd(fd,",\"temporary_owned\":")!=0||ds_num_fd(fd,r.temp_owned)!=0{return 0-1}
271 if ds_text_fd(fd,",\"cleanup_code\":")!=0||ds_num_fd(fd,r.cleanup_code)!=0{return 0-1}
272 if ds_text_fd(fd,",\"stage\":")!=0||ds_json_fd(fd,r.stage)!=0{return 0-1}
273 var verdict:*u8="REFUSED";if r.code==0{verdict="STAGED"}else{if r.visible==1{verdict="VISIBLE_UNCONFIRMED"}}
274 if ds_text_fd(fd,",\"verdict\":")!=0||ds_json_fd(fd,verdict)!=0{return 0-1}
275 return ds_text_fd(fd,"}\n")
276}
277// Legacy call contract retained. New callers use ds_emit_result with full state.
278func ds_emit(src:*u8,dst:*u8,rc:i64,sz:i64)->i64{
279 let r:*NxDeployStageResult=sys_mmap_try(__size_of(NxDeployStageResult)) as *NxDeployStageResult
280 if (r as i64)<=0{return 0-12};ds_stage_result_init(r);r.code=rc;r.bytes=sz;r.stage="legacy-state-unavailable"
281 let emitted:i64=ds_emit_result(1,src,dst,r)
282 sys_munmap_direct(r as *u8,__size_of(NxDeployStageResult));return emitted
283}
284
285// selftest: build a fake ELF fixture, stage it to a .new, verify byte-identical + size; refuse non-.new dst;
286// refuse a non-ELF src. 0 = GREEN.
287func ds_selftest() -> i64 {
288 var f: i64 = 0
289 // fixture: \x7fELF + 2000 bytes of pattern
290 let fx: *u8 = sys_mmap(DS_MAGIC_4096)
291 fx[0]=127 as u8; fx[1]=69 as u8; fx[2]=76 as u8; fx[3]=70 as u8
292 var i: i64 = 4
293 while i < DS_MAGIC_2000 { fx[i] = (65 + (i % 26)) as u8; i = i + 1 }
294 let wf: i64 = sys_openat_wr("dskat_src.elf" as *u8, 420)
295 if wf < 0 { return 1 }
296 sys_write(wf, fx, DS_MAGIC_2000); sys_close(wf)
297 // stage to a .new slot
298 let rc: i64 = ds_stage("dskat_src.elf" as *u8, "dskat_dst.elf.new" as *u8)
299 if rc != 0 { f = f + 1 }
300 // verify byte-identical + size
301 let vf: i64 = sys_openat_rd("dskat_dst.elf.new" as *u8)
302 if vf < 0 { f = f + 1 } else {
303 let vb: *u8 = sys_mmap(DS_MAGIC_4096)
304 let vn: i64 = sys_read(vf, vb, DS_MAGIC_4096)
305 sys_close(vf)
306 if vn != DS_MAGIC_2000 { f = f + 1 }
307 var j: i64 = 0
308 var mism: i64 = 0
309 while j < DS_MAGIC_2000 { if vb[j] != fx[j] { mism = 1; j = DS_MAGIC_2000 } else { j = j + 1 } }
310 if mism == 1 { f = f + 1 }
311 }
312 // refuse non-.new dst
313 let r2: i64 = ds_stage("dskat_src.elf" as *u8, "dskat_live.elf" as *u8)
314 if r2 != (0 - 2) { f = f + 1 }
315 // refuse non-ELF src
316 let bf: i64 = sys_openat_wr("dskat_bad.txt" as *u8, 420)
317 if bf >= 0 { sys_write(bf, "not an elf at all, plain text, over 1024 bytes..." as *u8, 48); sys_close(bf) }
318 let r3: i64 = ds_stage("dskat_bad.txt" as *u8, "dskat_bad.elf.new" as *u8)
319 if r3 != (0 - 4) { if r3 != (0 - 6) { f = f + 1 } } // -4 bad magic (or -6 too small); either refuses
320 return f
321}