ace-runtime 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/native/worker_gate.c CHANGED
@@ -3,7 +3,12 @@
3
3
  #include <fcntl.h>
4
4
  #include <json-c/json.h>
5
5
  #include <poll.h>
6
+ #include <openssl/crypto.h>
7
+ #include <openssl/evp.h>
8
+ #include <openssl/provider.h>
9
+ #include <sys/mman.h>
6
10
  #include <stdio.h>
11
+ #include <stdint.h>
7
12
  #include <stdlib.h>
8
13
  #include <string.h>
9
14
  #include <sys/prctl.h>
@@ -20,6 +25,7 @@ static _Noreturn void deny(const char *why) { fprintf(stderr, "ace-worker-gate:
20
25
  static double now(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC,&ts)) deny("clock unavailable"); return ts.tv_sec + ts.tv_nsec / 1e9; }
21
26
  static int token(const char *s) {
22
27
  if (!s || !*s || strlen(s)>128) return 0;
28
+ if(!((s[0]>='a'&&s[0]<='z')||(s[0]>='A'&&s[0]<='Z')||(s[0]>='0'&&s[0]<='9')))return 0;
23
29
  for (const unsigned char *p=(const unsigned char *)s; *p; p++)
24
30
  if (!((*p>='a'&&*p<='z')||(*p>='A'&&*p<='Z')||(*p>='0'&&*p<='9')||*p=='_'||*p=='-'||*p=='.')) return 0;
25
31
  return 1;
@@ -29,14 +35,18 @@ static json_object *field(json_object *o,const char *k,enum json_type t) {
29
35
  if (!o || !json_object_object_get_ex(o,k,&v) || !json_object_is_type(v,t)) deny("invalid protected schema");
30
36
  return v;
31
37
  }
32
- static const char *str(json_object *o,const char *k) { return json_object_get_string(field(o,k,json_type_string)); }
38
+ static const char *str(json_object *o,const char *k) {
39
+ json_object *value=field(o,k,json_type_string);const char *text=json_object_get_string(value);
40
+ if((size_t)json_object_get_string_len(value)!=strlen(text))deny("embedded NUL in protected string");
41
+ return text;
42
+ }
33
43
  static int number(json_object *o,const char *k) {
34
44
  int64_t n=json_object_get_int64(field(o,k,json_type_int));
35
45
  if (n<=0 || n>2147483647) deny("invalid principal identity");
36
46
  return (int)n;
37
47
  }
38
48
  static void trusted_path(const char *path,int dir,uid_t owner) {
39
- char copy[4096]; size_t n=strlen(path); struct stat st;
49
+ char copy[4097]; size_t n=strlen(path); struct stat st;
40
50
  if (!n || n>=sizeof(copy) || path[0]!='/' || strstr(path,"//") || strstr(path,"/../") || strstr(path,"/./") ||
41
51
  !strcmp(path+n-1,"/") || !strcmp(path+n-(n>=3?3:n),"/..")) deny("invalid protected path");
42
52
  memcpy(copy,path,n+1);
@@ -87,19 +97,179 @@ static void credentials(pid_t pid,int uid,int gid,json_object *groups) {
87
97
  }
88
98
  free(s); if(!got_uid||!got_gid||!got_groups||!no_new_privs||caps!=31)deny("kernel credentials unavailable");
89
99
  }
90
- static void lineage(json_object *native) {
91
- json_object *server=field(native,"server_identity",json_type_object); int pid=number(server,"pid");
92
- if(getppid()!=pid) deny("gate is not the original server child");
100
+ struct parent_identity { pid_t pid; char birth[256]; };
101
+ static void parent_birth(pid_t pid,char birth[256]) {
102
+ if(pid<=1||getppid()!=pid) deny("original gate parent unavailable");
93
103
  char path[64]; snprintf(path,sizeof(path),"/proc/%d/stat",pid); char *s=read_file(path,16384);
94
104
  char *end=strrchr(s,')'); if(!end||end[1]!=' ') deny("server birth unavailable");
95
105
  char *save=NULL,*word=strtok_r(end+2," ", &save); int i=3; const char *ticks=NULL;
96
106
  while(word) { if(i==22){ticks=word;break;} i++;word=strtok_r(NULL," ",&save); }
97
107
  if(!ticks||strspn(ticks,"0123456789")!=strlen(ticks))deny("server birth unavailable");
98
108
  char *boot=read_file("/proc/sys/kernel/random/boot_id",128); boot[strcspn(boot,"\n")]=0;
99
- char birth[256]; snprintf(birth,sizeof(birth),"linux:%s:%s",boot,ticks);
100
- if(strcmp(birth,str(server,"started_at")))deny("server incarnation changed");
109
+ int written=snprintf(birth,256,"linux:%s:%s",boot,ticks);
110
+ if(written<0||written>=256)deny("parent birth oversized");
101
111
  free(boot);free(s);
102
- credentials(pid,number(server,"uid"),number(server,"gid"),field(server,"groups",json_type_array));
112
+ if(getppid()!=pid)deny("original gate parent changed");
113
+ }
114
+ static void same_parent(const struct parent_identity *parent) {
115
+ char birth[256];parent_birth(parent->pid,birth);
116
+ if(strcmp(birth,parent->birth))deny("original gate parent incarnation changed");
117
+ }
118
+ static void parent_credentials(const struct parent_identity *parent,json_object *mapping) {
119
+ same_parent(parent);
120
+ credentials(parent->pid,number(mapping,"worker_uid"),number(mapping,"worker_gid"),field(mapping,"worker_groups",json_type_array));
121
+ same_parent(parent);
122
+ }
123
+ static void closed(json_object *object,const char *const *keys,size_t count) {
124
+ if(!json_object_is_type(object,json_type_object)||(size_t)json_object_object_length(object)!=count)deny("open protected schema");
125
+ for(size_t i=0;i<count;i++) { json_object *value=NULL;if(!json_object_object_get_ex(object,keys[i],&value))deny("incomplete protected schema"); }
126
+ }
127
+ static void hex_field(json_object *object,const char *key,size_t min,size_t max) {
128
+ const char *value=str(object,key);size_t length=(size_t)json_object_get_string_len(field(object,key,json_type_string));
129
+ if(length!=strlen(value)||(length!=min&&length!=max)||strspn(value,"0123456789abcdef")!=length)deny("invalid protected digest");
130
+ }
131
+ static void bounded_integer(json_object *object,const char *key,int64_t max) {
132
+ int64_t value=json_object_get_int64(field(object,key,json_type_int));
133
+ if(value<1||value>max)deny("invalid prepared bound");
134
+ }
135
+ static void entry_reference(json_object *reference,int64_t limit) {
136
+ static const char *const keys[]={"path","bytes","sha256"};
137
+ closed(reference,keys,3);bounded_integer(reference,"bytes",limit);hex_field(reference,"sha256",64,64);
138
+ const char *path=str(reference,"path");size_t n=strlen(path);
139
+ if(n<2||n>4096||path[0]!='/'||path[n-1]=='/'||strstr(path,"//")||strstr(path,"/./")||strstr(path,"/../")||
140
+ !strcmp(path+n-2,"/.")||(n>=3&&!strcmp(path+n-3,"/..")))deny("invalid worker reference path");
141
+ }
142
+ static void worker_entry(json_object *entry) {
143
+ static const char *const keys[]={"interpreter","wrapper"};closed(entry,keys,2);
144
+ entry_reference(field(entry,"interpreter",json_type_object),33554432);
145
+ entry_reference(field(entry,"wrapper",json_type_object),1048576);
146
+ }
147
+ static void workspace_resource(json_object *resource) {
148
+ static const char *const keys[]={"device","filesystem_type","gid","host_path","inode","mount_id","uid","view_path"};
149
+ closed(resource,keys,8);
150
+ const char *numbers[]={"device","inode","uid","gid"};
151
+ for(size_t i=0;i<4;i++)if(json_object_get_int64(field(resource,numbers[i],json_type_int))<0)deny("invalid workspace identity");
152
+ bounded_integer(resource,"mount_id",INT64_MAX);
153
+ const char *fs=str(resource,"filesystem_type");
154
+ if(strcmp(fs,"ext4")&&strcmp(fs,"xfs")&&strcmp(fs,"btrfs")&&strcmp(fs,"tmpfs"))deny("invalid workspace filesystem");
155
+ const char *paths[]={"host_path","view_path"};
156
+ for(size_t i=0;i<2;i++) {
157
+ const char *path=str(resource,paths[i]);size_t n=strlen(path);
158
+ if(!n||n>4096||path[0]!='/'||(n>1&&path[n-1]=='/')||strstr(path,"//")||strstr(path,"/./")||strstr(path,"/../")||
159
+ (n>=2&&!strcmp(path+n-2,"/."))||(n>=3&&!strcmp(path+n-3,"/..")))deny("invalid workspace path");
160
+ }
161
+ }
162
+ static void workspace_exclusion(json_object *projection,const char *mapping_id) {
163
+ static const char *const keys[]={"authority_gid","authority_uid","key","root_resource","worker_cwd_resource"};
164
+ closed(projection,keys,5);number(projection,"authority_uid");number(projection,"authority_gid");
165
+ json_object *root=field(projection,"root_resource",json_type_object);
166
+ workspace_resource(root);workspace_resource(field(projection,"worker_cwd_resource",json_type_object));
167
+ char view[256],prefix[256];
168
+ int v=snprintf(view,sizeof(view),"/run/ace/lifecycle-exclusion/%s",mapping_id);
169
+ int p=snprintf(prefix,sizeof(prefix),"workspace:%s:",mapping_id);
170
+ const char *key=str(projection,"key");size_t n=strlen(key);
171
+ if(v<0||(size_t)v>=sizeof(view)||p<0||(size_t)p>=sizeof(prefix)||strcmp(view,str(root,"view_path"))||
172
+ n!=(size_t)p+64||strncmp(key,prefix,(size_t)p))deny("workspace mapping differs");
173
+ for(size_t i=(size_t)p;i<n;i++)if(!((key[i]>='0'&&key[i]<='9')||(key[i]>='a'&&key[i]<='f')))deny("invalid workspace digest");
174
+ char suffix[256];int s=snprintf(suffix,sizeof(suffix),"/workspaces/%s/%s",mapping_id,key+p);
175
+ const char *host=str(root,"host_path");size_t h=strlen(host);
176
+ if(s<0||(size_t)s>=sizeof(suffix)||h<(size_t)s||strcmp(host+h-s,suffix)||
177
+ json_object_get_int64(field(root,"uid",json_type_int))!=number(projection,"authority_uid")||
178
+ json_object_get_int64(field(root,"gid",json_type_int))!=number(projection,"authority_gid"))deny("workspace owner differs");
179
+ /* The original Ruby owner verifies the canonical cwd digest and held inode.
180
+ These closed release fields convey no independent filesystem grant. */
181
+ }
182
+ static void prepared_permission(json_object *permission,const char *mapping_id) {
183
+ static const char *const permission_keys[]={"operation","launch_ticket","attempt_id","assignment_id","generation","journal_commit","prepared_input"};
184
+ closed(permission,permission_keys,7);hex_field(permission,"journal_commit",40,64);
185
+ json_object *input=field(permission,"prepared_input",json_type_object);
186
+ static const char *const input_keys[]={"registration_generation","registration_commit","definition_digest","original_binding_digest","prepared_work","bundle_ref","bundle_bytes","bundle_sha256","worker_entry","workspace_exclusion"};
187
+ closed(input,input_keys,10);worker_entry(field(input,"worker_entry",json_type_object));bounded_integer(input,"registration_generation",INT64_MAX);
188
+ workspace_exclusion(field(input,"workspace_exclusion",json_type_object),mapping_id);
189
+ hex_field(input,"registration_commit",40,64);hex_field(input,"definition_digest",64,64);
190
+ hex_field(input,"original_binding_digest",64,64);hex_field(input,"bundle_sha256",64,64);
191
+ bounded_integer(input,"bundle_bytes",64*1024*1024);
192
+ char expected[256];
193
+ int written=snprintf(expected,sizeof(expected),"execution/prepared/%s-%s.bundle",str(permission,"assignment_id"),str(input,"bundle_sha256"));
194
+ if(written<0||(size_t)written>=sizeof(expected)||
195
+ strcmp(expected,str(input,"bundle_ref"))||json_object_get_string_len(field(input,"bundle_ref",json_type_string))!=(int)strlen(expected))deny("prepared bundle reference differs");
196
+ json_object *work=field(input,"prepared_work",json_type_object);
197
+ static const char *const work_keys[]={"version","task_id","scope","prepared_head","prepared_tree","manifest_bytes","manifest_sha256","selection_sha256"};
198
+ closed(work,work_keys,8);
199
+ const char *task=str(work,"task_id");size_t task_length=(size_t)json_object_get_string_len(field(work,"task_id",json_type_string));
200
+ if(json_object_get_int64(field(work,"version",json_type_int))!=1||task_length!=strlen(task)||!task_length||task_length>200||
201
+ !((task[0]>='a'&&task[0]<='z')||(task[0]>='A'&&task[0]<='Z')||(task[0]>='0'&&task[0]<='9')))deny("invalid prepared selection");
202
+ for(size_t i=0;i<task_length;i++)if(!((task[i]>='a'&&task[i]<='z')||(task[i]>='A'&&task[i]<='Z')||(task[i]>='0'&&task[i]<='9')||task[i]=='_'||task[i]=='-'||task[i]=='.'))deny("invalid prepared task");
203
+ const char *scope=str(work,"scope");size_t scope_length=(size_t)json_object_get_string_len(field(work,"scope",json_type_string));
204
+ if(scope_length!=strlen(scope)||!scope_length)deny("invalid prepared scope");
205
+ size_t digits=0;
206
+ for(size_t i=0;i<scope_length;i++) {
207
+ if(scope[i]>='0'&&scope[i]<='9')digits++;
208
+ else if(scope[i]=='.'&&digits>0)digits=0;
209
+ else deny("invalid prepared scope");
210
+ }
211
+ if(!digits)deny("invalid prepared scope");
212
+ hex_field(work,"prepared_head",40,64);hex_field(work,"prepared_tree",40,64);
213
+ hex_field(work,"manifest_sha256",64,64);hex_field(work,"selection_sha256",64,64);
214
+ bounded_integer(work,"manifest_bytes",32768);
215
+ }
216
+ /* First OpenSSL call suppresses config; a private context has only the fixed
217
+ default provider. /dev/null is deliberately not a module directory. */
218
+ static EVP_MD *fixed_sha256(OSSL_LIB_CTX **context,OSSL_PROVIDER **provider) {
219
+ if(!OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG,NULL))deny("digest initialization failed");
220
+ *context=OSSL_LIB_CTX_new();if(!*context||!OSSL_PROVIDER_set_default_search_path(*context,"/dev/null"))deny("digest context failed");
221
+ *provider=OSSL_PROVIDER_load(*context,"default");if(!*provider)deny("fixed digest provider unavailable");
222
+ EVP_MD *digest=EVP_MD_fetch(*context,"SHA256","provider=default");if(!digest)deny("fixed digest unavailable");return digest;
223
+ }
224
+ static int same_metadata(const struct stat *a,const struct stat *b) {
225
+ return a->st_dev==b->st_dev&&a->st_ino==b->st_ino&&a->st_uid==b->st_uid&&a->st_gid==b->st_gid&&
226
+ a->st_mode==b->st_mode&&a->st_size==b->st_size&&a->st_mtim.tv_sec==b->st_mtim.tv_sec&&
227
+ a->st_mtim.tv_nsec==b->st_mtim.tv_nsec&&a->st_ctim.tv_sec==b->st_ctim.tv_sec&&a->st_ctim.tv_nsec==b->st_ctim.tv_nsec;
228
+ }
229
+ static int held_entry(json_object *reference,int executable,const EVP_MD *digest,struct stat *verified) {
230
+ const char *path=str(reference,"path");root_path(path,0);
231
+ int fd=open(path,O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);if(fd<0)deny("worker entry unavailable");
232
+ struct stat before,after;
233
+ int64_t expected=json_object_get_int64(field(reference,"bytes",json_type_int));
234
+ if(fstat(fd,&before)||!S_ISREG(before.st_mode)||before.st_uid!=0||(before.st_mode&06022)||before.st_size!=expected||
235
+ (executable&&!(before.st_mode&0111)))deny("unsafe held worker entry");
236
+ unsigned char magic[4];
237
+ if(executable&&(pread(fd,magic,sizeof(magic),0)!=(ssize_t)sizeof(magic)||memcmp(magic,"\177ELF",4)))deny("worker interpreter is not native ELF");
238
+ int snapshot=-1;if(!executable) {snapshot=memfd_create("ace-worker-source",MFD_CLOEXEC|MFD_ALLOW_SEALING);if(snapshot<0)deny("worker snapshot unavailable");}
239
+ EVP_MD_CTX *ctx=EVP_MD_CTX_new();if(!ctx||!EVP_DigestInit_ex(ctx,digest,NULL))deny("worker digest unavailable");
240
+ unsigned char buffer[65536],sum[EVP_MAX_MD_SIZE];size_t total=0;unsigned int sum_size=0;
241
+ for(;;) {
242
+ ssize_t n=read(fd,buffer,sizeof(buffer));if(n<0&&errno==EINTR)continue;if(n<0)deny("held worker read failed");if(!n)break;
243
+ if((int64_t)total+n>expected)deny("held worker entry enlarged");
244
+ if(!EVP_DigestUpdate(ctx,buffer,(size_t)n))deny("worker digest failed");
245
+ total+=(size_t)n;
246
+ if(snapshot>=0) {size_t offset=0;while(offset<(size_t)n) {ssize_t written=write(snapshot,buffer+offset,(size_t)n-offset);
247
+ if(written<0&&errno==EINTR)continue;
248
+ if(written<=0)deny("worker snapshot write failed");
249
+ offset+=(size_t)written;}}
250
+ }
251
+ if(total!=(size_t)expected||!EVP_DigestFinal_ex(ctx,sum,&sum_size)||sum_size!=32||fstat(fd,&after)||!same_metadata(&before,&after))deny("held worker entry changed");
252
+ EVP_MD_CTX_free(ctx);char hex[65];for(unsigned int i=0;i<32;i++)snprintf(hex+i*2,3,"%02x",sum[i]);
253
+ if(strcmp(hex,str(reference,"sha256")))deny("held worker digest differs");
254
+ *verified=after;
255
+ if(executable) {if(lseek(fd,0,SEEK_SET)<0)deny("worker interpreter seek failed");return fd;}
256
+ close(fd);
257
+ int seals=F_SEAL_WRITE|F_SEAL_GROW|F_SEAL_SHRINK|F_SEAL_SEAL;
258
+ if(fcntl(snapshot,F_ADD_SEALS,seals)||fcntl(snapshot,F_GET_SEALS)!=seals)deny("worker snapshot sealing failed");
259
+ char descriptor[64];snprintf(descriptor,sizeof(descriptor),"/proc/self/fd/%d",snapshot);
260
+ int readonly=open(descriptor,O_RDONLY|O_CLOEXEC);if(readonly<0)deny("readonly worker snapshot unavailable");
261
+ struct stat sealed,reopened;
262
+ if(fstat(snapshot,&sealed)||fstat(readonly,&reopened)||sealed.st_dev!=reopened.st_dev||sealed.st_ino!=reopened.st_ino||
263
+ reopened.st_size!=expected||fcntl(readonly,F_GET_SEALS)!=seals)deny("worker snapshot identity differs");
264
+ *verified=reopened;
265
+ close(snapshot);return readonly;
266
+ }
267
+ static void select_entry_descriptors(int interpreter,int wrapper) {
268
+ int held_interpreter=fcntl(interpreter,F_DUPFD_CLOEXEC,6),held_wrapper=fcntl(wrapper,F_DUPFD_CLOEXEC,6);
269
+ if(held_interpreter<0||held_wrapper<0)deny("worker descriptor reservation failed");
270
+ close(interpreter);close(wrapper);
271
+ if(dup2(held_interpreter,4)!=4||dup2(held_wrapper,5)!=5||fcntl(4,F_SETFD,0)||fcntl(5,F_SETFD,0))deny("worker descriptor selection failed");
272
+ close(held_interpreter);close(held_wrapper);
103
273
  }
104
274
  static void wait_fd(int fd,short events,double deadline) {
105
275
  struct pollfd p={.fd=fd,.events=events}; int r;
@@ -125,26 +295,32 @@ static json_object *receive(int fd,double deadline) {
125
295
  while(n<LIMIT) { wait_fd(fd,POLLIN,deadline); ssize_t r=recv(fd,s+n,1,0);
126
296
  if(r<0&&(errno==EINTR||errno==EAGAIN))continue;
127
297
  if(r<=0)deny("authority EOF before permission");
128
- if(s[n++]=='\n') { json_tokener *t=json_tokener_new();json_object *o=json_tokener_parse_ex(t,s,(int)n);
298
+ if(s[n++]=='\n') { json_tokener *t=json_tokener_new();json_tokener_set_flags(t,JSON_TOKENER_VALIDATE_UTF8);json_object *o=json_tokener_parse_ex(t,s,(int)n);
129
299
  if(!o||json_tokener_get_error(t)!=json_tokener_success||json_tokener_get_parse_end(t)!=n||!json_object_is_type(o,json_type_object))deny("invalid authority frame");
300
+ /* The maintained wire emits one compact JSON record. Round-trip equality
301
+ refuses duplicate keys, extra whitespace/records and alternate escapes. */
302
+ const char *canonical=json_object_to_json_string_ext(o,JSON_C_TO_STRING_PLAIN|JSON_C_TO_STRING_NOSLASHESCAPE);
303
+ if(strlen(canonical)!=n-1||memcmp(canonical,s,n-1))deny("noncanonical authority frame");
130
304
  json_tokener_free(t);free(s);return o; }
131
305
  }
132
306
  deny("authority frame oversized"); return NULL;
133
307
  }
134
308
  int main(int argc,char **argv) {
309
+ struct parent_identity original_parent={.pid=getppid()};
310
+ parent_birth(original_parent.pid,original_parent.birth);same_parent(&original_parent);
135
311
  if(prctl(PR_SET_DUMPABLE,0,0,0,0))deny("non-dumpable gate unavailable");
136
312
  if(argc!=3||!token(argv[1])||!token(argv[2]))deny("expected fixed mapping and correlation ticket");
137
313
  policy();root_path(MAP_PATH,0);char *bytes=read_file(MAP_PATH,LIMIT);
138
- json_tokener *parser=json_tokener_new();json_object *map=json_tokener_parse_ex(parser,bytes,(int)strlen(bytes));
314
+ json_tokener *parser=json_tokener_new();json_tokener_set_flags(parser,JSON_TOKENER_VALIDATE_UTF8);json_object *map=json_tokener_parse_ex(parser,bytes,(int)strlen(bytes));
139
315
  if(!map||json_tokener_get_error(parser)!=json_tokener_success||json_tokener_get_parse_end(parser)!=strlen(bytes))deny("invalid deployment map");
140
316
  json_tokener_free(parser);free(bytes);
141
- if(strcmp(str(map,"schema"),"ace.assign.authorities/v1"))deny("unsupported deployment map");
317
+ if(strcmp(str(map,"schema"),"ace.assign.authorities/v2"))deny("unsupported deployment map");
142
318
  json_object *mapping=field(field(map,"launch_mappings",json_type_object),argv[1],json_type_object);
143
319
  json_object *service=field(field(map,"authorities",json_type_object),str(mapping,"authority_id"),json_type_object);
144
320
  int worker=number(mapping,"worker_uid"),launcher=number(mapping,"launcher_uid"),authority=number(service,"uid");
145
321
  if(worker==launcher||worker==authority||authority==launcher)deny("principals are not distinct");
146
322
  credentials(getpid(),worker,number(mapping,"worker_gid"),field(mapping,"worker_groups",json_type_array));
147
- lineage(field(mapping,"native",json_type_object));
323
+ parent_credentials(&original_parent,mapping);
148
324
  const char *bootstrap=str(mapping,"bootstrap");root_path(bootstrap,0);
149
325
  struct stat installed,self;
150
326
  if(stat(bootstrap,&installed)||stat("/proc/self/exe",&self)||installed.st_dev!=self.st_dev||installed.st_ino!=self.st_ino||
@@ -171,7 +347,7 @@ int main(int argc,char **argv) {
171
347
  json_object_object_add(request,"version",json_object_new_int(1));json_object_object_add(request,"operation",json_object_new_string("gate_ready"));
172
348
  json_object_object_add(request,"mutation_id",NULL);json_object_object_add(request,"project_id",json_object_new_string(str(mapping,"project_id")));
173
349
  json_object_object_add(params,"mapping_id",json_object_new_string(argv[1]));json_object_object_add(params,"launch_ticket",json_object_new_string(argv[2]));
174
- json_object_object_add(request,"params",params);send_frame(fd,request,deadline);json_object_put(request);
350
+ json_object_object_add(request,"params",params);parent_credentials(&original_parent,mapping);send_frame(fd,request,deadline);json_object_put(request);
175
351
  json_object *ready=receive(fd,deadline);
176
352
  if(strcmp(str(ready,"status"),"ok")||strcmp(str(field(ready,"data",json_type_object),"phase"),"ready"))deny("gate not admitted");
177
353
  json_object_put(ready);
@@ -179,12 +355,15 @@ int main(int argc,char **argv) {
179
355
  if(strcmp(str(permission,"operation"),"release")||strcmp(str(permission,"launch_ticket"),argv[2])||
180
356
  !token(str(permission,"attempt_id"))||!token(str(permission,"assignment_id"))||!token(str(permission,"journal_commit")))deny("release permission differs");
181
357
  if(json_object_get_int64(field(permission,"generation",json_type_int))<1)deny("invalid release generation");
182
- policy();lineage(field(mapping,"native",json_type_object));credentials(peer.pid,authority,number(service,"gid"),field(service,"groups",json_type_array));
183
- json_object *arguments=field(mapping,"worker_argv",json_type_array);size_t count=json_object_array_length(arguments);
184
- if(!count||count>64)deny("invalid fixed payload");
185
- char **payload=calloc(count+1,sizeof(char *));if(!payload)deny("allocation failed");
186
- for(size_t i=0;i<count;i++) {json_object *value=json_object_array_get_idx(arguments,i);if(!json_object_is_type(value,json_type_string))deny("invalid fixed argv");payload[i]=(char *)json_object_get_string(value);}
187
- root_path(payload[0],0);struct stat program;if(stat(payload[0],&program)||(program.st_mode&06000)||!(program.st_mode&0111))deny("unsafe payload executable");
358
+ if(strlen(json_object_to_json_string_ext(permission,JSON_C_TO_STRING_PLAIN|JSON_C_TO_STRING_NOSLASHESCAPE))+1>16384)deny("release envelope oversized");
359
+ prepared_permission(permission,argv[1]);
360
+ policy();parent_credentials(&original_parent,mapping);credentials(peer.pid,authority,number(service,"gid"),field(service,"groups",json_type_array));
361
+ json_object *entry=field(field(permission,"prepared_input",json_type_object),"worker_entry",json_type_object);
362
+ OSSL_LIB_CTX *digest_context=NULL;OSSL_PROVIDER *digest_provider=NULL;EVP_MD *digest=fixed_sha256(&digest_context,&digest_provider);
363
+ struct stat interpreter_metadata,wrapper_metadata;
364
+ int interpreter=held_entry(field(entry,"interpreter",json_type_object),1,digest,&interpreter_metadata);
365
+ int wrapper=held_entry(field(entry,"wrapper",json_type_object),0,digest,&wrapper_metadata);
366
+ EVP_MD_free(digest);OSSL_PROVIDER_unload(digest_provider);OSSL_LIB_CTX_free(digest_context);
188
367
  if(clearenv())deny("environment reset failed");
189
368
  json_object *env=field(mapping,"worker_env",json_type_object);
190
369
  json_object_object_foreach(env,key,value) {
@@ -195,5 +374,12 @@ int main(int argc,char **argv) {
195
374
  setenv("ACE_ASSIGN_LAUNCH_MAPPING",argv[1],1))deny("authority context unavailable");
196
375
  if(chdir(str(mapping,"worker_cwd")))deny("fixed worker directory unavailable");
197
376
  close(fd);
198
- execv(payload[0],payload);deny("fixed worker execution failed");
377
+ struct stat final_interpreter,final_wrapper;
378
+ if(fstat(interpreter,&final_interpreter)||fstat(wrapper,&final_wrapper)||
379
+ !same_metadata(&interpreter_metadata,&final_interpreter)||!same_metadata(&wrapper_metadata,&final_wrapper))deny("worker entry changed before execution");
380
+ select_entry_descriptors(interpreter,wrapper);
381
+ parent_credentials(&original_parent,mapping);
382
+ char *const payload[]={"/proc/self/fd/4","-I","-S","-B","/proc/self/fd/5","authority","worker",NULL};
383
+ extern char **environ;
384
+ fexecve(4,payload,environ);deny("fixed worker execution failed");
199
385
  }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-05 00:00:00.000000000 Z
10
+ date: 2026-10-10 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: fiddle
@@ -201,14 +201,23 @@ files:
201
201
  - lib/ace/runtime/cli/commands/send.rb
202
202
  - lib/ace/runtime/errors.rb
203
203
  - lib/ace/runtime/molecules/cgroup_observation.rb
204
+ - lib/ace/runtime/molecules/codex_runtime_installation.rb
205
+ - lib/ace/runtime/molecules/execution_boot_baseline.rb
206
+ - lib/ace/runtime/molecules/execution_network_selection.rb
204
207
  - lib/ace/runtime/molecules/execution_unit_installation.rb
208
+ - lib/ace/runtime/molecules/kernel_view_topology.rb
205
209
  - lib/ace/runtime/molecules/linux_mount_info.rb
206
210
  - lib/ace/runtime/molecules/network_installation_evidence.rb
207
211
  - lib/ace/runtime/molecules/process_identity.rb
208
212
  - lib/ace/runtime/molecules/protected_artifact_set.rb
209
213
  - lib/ace/runtime/molecules/protected_linux.rb
210
214
  - lib/ace/runtime/molecules/protected_socket.rb
215
+ - lib/ace/runtime/molecules/protected_task_context_entry.rb
216
+ - lib/ace/runtime/molecules/protected_worker_entry.rb
217
+ - lib/ace/runtime/molecules/readiness_configuration.rb
218
+ - lib/ace/runtime/molecules/readiness_runtime.rb
211
219
  - lib/ace/runtime/molecules/runtime_selector.rb
220
+ - lib/ace/runtime/molecules/server_resource_observation.rb
212
221
  - lib/ace/runtime/molecules/systemd_scope_manager.rb
213
222
  - lib/ace/runtime/registry.rb
214
223
  - lib/ace/runtime/testing.rb