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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/docs/usage.md +55 -0
- data/lib/ace/runtime/molecules/codex_runtime_installation.rb +130 -0
- data/lib/ace/runtime/molecules/execution_boot_baseline.rb +134 -0
- data/lib/ace/runtime/molecules/execution_network_selection.rb +85 -0
- data/lib/ace/runtime/molecules/execution_unit_installation.rb +375 -21
- data/lib/ace/runtime/molecules/kernel_view_topology.rb +133 -0
- data/lib/ace/runtime/molecules/protected_artifact_set.rb +74 -9
- data/lib/ace/runtime/molecules/protected_socket.rb +24 -2
- data/lib/ace/runtime/molecules/protected_task_context_entry.rb +162 -0
- data/lib/ace/runtime/molecules/protected_worker_entry.rb +32 -0
- data/lib/ace/runtime/molecules/readiness_configuration.rb +154 -0
- data/lib/ace/runtime/molecules/readiness_runtime.rb +94 -0
- data/lib/ace/runtime/molecules/server_resource_observation.rb +183 -0
- data/lib/ace/runtime/molecules/systemd_scope_manager.rb +103 -15
- data/lib/ace/runtime/version.rb +1 -1
- data/lib/ace/runtime.rb +1 -0
- data/native/README.md +61 -6
- data/native/build-worker-gate +4 -2
- data/native/worker_gate.c +206 -20
- metadata +11 -2
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) {
|
|
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[
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
if(getppid()!=pid) deny("gate
|
|
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
|
-
|
|
100
|
-
if(
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|