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}