@ai-ecoverse/slicc-kernel 1.80.0 → 1.82.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.
package/dist/node.js CHANGED
@@ -570,6 +570,7 @@ function id(value) {
570
570
  return value;
571
571
  }
572
572
  function changeCred(cur, change) {
573
+ if (typeof change !== "object" || change === null) throw fsError("EINVAL", "not a change");
573
574
  const privileged = cur.euid === 0;
574
575
  const next2 = copyCred(cur);
575
576
  for (const keys of [UIDS, GIDS]) {
@@ -3032,6 +3033,97 @@ var MemoryMeta = class {
3032
3033
  }
3033
3034
  };
3034
3035
 
3036
+ // src/fs/integrity.ts
3037
+ var KERNEL_CALLER = { pid: 0, uid: 0 };
3038
+ function integrityOptions(options) {
3039
+ if (options && typeof options.lookup !== "function") {
3040
+ throw new TypeError("protect.integrity.lookup must be a function");
3041
+ }
3042
+ integrityUnknown(options);
3043
+ }
3044
+ function integrityUnknown(options) {
3045
+ const unknown = options?.unknown ?? "allow";
3046
+ if (unknown !== "allow" && unknown !== "deny") {
3047
+ throw new TypeError("protect.integrity.unknown must be 'allow' or 'deny'");
3048
+ }
3049
+ return unknown;
3050
+ }
3051
+ function hex(digest) {
3052
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
3053
+ }
3054
+ async function sha256(bytes) {
3055
+ return hex(await crypto.subtle.digest("SHA-256", bytes));
3056
+ }
3057
+ var Integrity = class {
3058
+ prefixes;
3059
+ root;
3060
+ lookup;
3061
+ unknown;
3062
+ report;
3063
+ unverified = /* @__PURE__ */ new Set();
3064
+ constructor(prefixes2, root, options, report) {
3065
+ this.prefixes = prefixes2;
3066
+ this.root = root;
3067
+ this.lookup = options.lookup;
3068
+ this.unknown = integrityUnknown(options);
3069
+ this.report = report;
3070
+ }
3071
+ async real(path) {
3072
+ const at = normalizePath(path);
3073
+ const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
3074
+ return isProtected(this.prefixes, real) ? real : void 0;
3075
+ }
3076
+ async key(path) {
3077
+ const real = await this.real(path);
3078
+ if (real === void 0 || !this.root.version) return void 0;
3079
+ return `${real}\0${await this.root.version(real)}`;
3080
+ }
3081
+ async read(path, caller, plain) {
3082
+ return (await this.readKeyed(path, caller, plain)).bytes;
3083
+ }
3084
+ async readKeyed(path, caller, plain) {
3085
+ const real = await this.real(path);
3086
+ if (real === void 0) return { bytes: await plain() };
3087
+ const read = this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
3088
+ const { bytes } = read;
3089
+ const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
3090
+ const actual = await sha256(bytes);
3091
+ const denial = { pid: caller.pid, uid: caller.uid, path: real, actual };
3092
+ const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
3093
+ let expected;
3094
+ try {
3095
+ expected = await this.expected(real);
3096
+ } catch {
3097
+ this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
3098
+ throw fsError("EACCES", `integrity lookup failed: ${real}`);
3099
+ }
3100
+ if (expected === void 0) {
3101
+ if (this.unknown === "allow") {
3102
+ if (!this.unverified.has(real)) {
3103
+ this.unverified.add(real);
3104
+ this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
3105
+ }
3106
+ return keyed;
3107
+ }
3108
+ } else if (expected === actual) {
3109
+ return keyed;
3110
+ }
3111
+ const known = expected !== void 0 ? { expected } : {};
3112
+ this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
3113
+ throw fsError("EACCES", `integrity check failed: ${real}`);
3114
+ }
3115
+ async expected(path) {
3116
+ const answer = await this.lookup(path);
3117
+ return typeof answer === "string" ? answer.toLowerCase() : void 0;
3118
+ }
3119
+ say(event) {
3120
+ try {
3121
+ this.report(event);
3122
+ } catch {
3123
+ }
3124
+ }
3125
+ };
3126
+
3035
3127
  // src/fs/protect.ts
3036
3128
  function writerTags(protection) {
3037
3129
  const writers = protection?.writers ?? [];
@@ -3884,6 +3976,29 @@ var OpfsFs = class {
3884
3976
  false
3885
3977
  );
3886
3978
  }
3979
+ async readVersioned(path) {
3980
+ const handle = await this.existing(path);
3981
+ return this.retrying(
3982
+ path,
3983
+ async () => {
3984
+ const file = await handle.getFile();
3985
+ const bytes = new Uint8Array(await file.arrayBuffer());
3986
+ return { bytes, version: `${file.lastModified}:${file.size}` };
3987
+ },
3988
+ false
3989
+ );
3990
+ }
3991
+ async version(path) {
3992
+ const handle = await this.existing(path);
3993
+ return this.retrying(
3994
+ path,
3995
+ async () => {
3996
+ const file = await handle.getFile();
3997
+ return `${file.lastModified}:${file.size}`;
3998
+ },
3999
+ false
4000
+ );
4001
+ }
3887
4002
  async pread(path, offset, length2) {
3888
4003
  const handle = await this.existing(path);
3889
4004
  return this.retrying(
@@ -5538,7 +5653,7 @@ function owned(path, st) {
5538
5653
  function unchanged(a, b, bytes) {
5539
5654
  return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
5540
5655
  }
5541
- async function sha256(bytes) {
5656
+ async function sha2562(bytes) {
5542
5657
  const digest = new Uint8Array(
5543
5658
  await crypto.subtle.digest("SHA-256", bytes)
5544
5659
  );
@@ -5594,7 +5709,7 @@ async function attest(fs, asked, o) {
5594
5709
  const bytes = (await fs.readFileBuffer(path)).slice();
5595
5710
  const after = await fs.lstat(path);
5596
5711
  if (unchanged(before, after, bytes)) {
5597
- return attested(path, after, bytes, await sha256(bytes), o, parents);
5712
+ return attested(path, after, bytes, await sha2562(bytes), o, parents);
5598
5713
  }
5599
5714
  if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
5600
5715
  }
@@ -16294,6 +16409,13 @@ var Launcher = class {
16294
16409
  unwatchScopes = () => void 0;
16295
16410
  warned = /* @__PURE__ */ new Set();
16296
16411
  compiled = /* @__PURE__ */ new Map();
16412
+ texts = /* @__PURE__ */ new Map();
16413
+ integrity;
16414
+ scannedFs;
16415
+ get scanned() {
16416
+ this.scannedFs ??= this.verifiedFs(this.base);
16417
+ return this.scannedFs;
16418
+ }
16297
16419
  processes = /* @__PURE__ */ new Map();
16298
16420
  zombies = /* @__PURE__ */ new Set();
16299
16421
  orphans = /* @__PURE__ */ new Set();
@@ -16397,7 +16519,7 @@ var Launcher = class {
16397
16519
  if (options.uplink) this.net.useUplink({ uplink: options.uplink, routes: this.routes });
16398
16520
  enableNetwork(this.net, {
16399
16521
  transport: this.transport,
16400
- engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
16522
+ engine: kernelTlsEngine(packageTlsEngine(this.verifiedFs(options.fs), this.modulesDir)),
16401
16523
  ca: this.ca
16402
16524
  });
16403
16525
  enableCdp(this.net, this.cdp);
@@ -16432,6 +16554,16 @@ var Launcher = class {
16432
16554
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
16433
16555
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
16434
16556
  options.fs.protect?.(this.prefixes);
16557
+ const integrity = options.protect?.integrity;
16558
+ integrityOptions(integrity);
16559
+ if (integrity && this.prefixes.length > 0) {
16560
+ this.integrity = new Integrity(
16561
+ this.prefixes,
16562
+ options.fs,
16563
+ integrity,
16564
+ (event) => options.onDenial?.(event)
16565
+ );
16566
+ }
16435
16567
  this.users = new UserDb(this.fs, this.owners);
16436
16568
  this.sudoer = new Sudo({
16437
16569
  fs: this.fs,
@@ -16477,7 +16609,7 @@ var Launcher = class {
16477
16609
  commands(scope = "") {
16478
16610
  let found = this.catalogs.get(scope);
16479
16611
  if (!found) {
16480
- found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
16612
+ found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
16481
16613
  this.catalogs.set(scope, found);
16482
16614
  }
16483
16615
  return found;
@@ -16485,7 +16617,7 @@ var Launcher = class {
16485
16617
  ownCommands(scope) {
16486
16618
  let found = this.ownCatalogs.get(scope);
16487
16619
  if (!found) {
16488
- found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
16620
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => new Set(commands.keys()));
16489
16621
  this.ownCatalogs.set(scope, found);
16490
16622
  }
16491
16623
  return found;
@@ -16494,7 +16626,7 @@ var Launcher = class {
16494
16626
  let found = this.binfmts.get(scope);
16495
16627
  if (!found) {
16496
16628
  found = this.roots(scope).then(
16497
- (roots) => scanBinfmts(this.base, roots, (message3) => {
16629
+ (roots) => scanBinfmts(this.scanned, roots, (message3) => {
16498
16630
  if (this.warned.has(message3)) return;
16499
16631
  this.warned.add(message3);
16500
16632
  console.warn(message3);
@@ -16613,10 +16745,16 @@ var Launcher = class {
16613
16745
  }
16614
16746
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
16615
16747
  const via = [interp, ...inner?.via ?? []];
16616
- return inner && { ...inner, target: withScriptEnv(inner.target, command), via };
16748
+ const sources = [script, ...inner?.sources ?? []];
16749
+ return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
16617
16750
  }
16618
16751
  const target = withScriptEnv(found, command);
16619
- return { target, args: [...target.prefix ?? [], ...passed], via: [interp] };
16752
+ return {
16753
+ target,
16754
+ args: [...target.prefix ?? [], ...passed],
16755
+ via: [interp],
16756
+ sources: [script]
16757
+ };
16620
16758
  }
16621
16759
  async plan(file, argv, cwd, scope, argv0) {
16622
16760
  const found = await this.resolve(file, argv[0] ?? file, cwd, scope);
@@ -16648,7 +16786,7 @@ var Launcher = class {
16648
16786
  }
16649
16787
  async binfmtManifests(scripts) {
16650
16788
  const sources = /* @__PURE__ */ new Map();
16651
- const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
16789
+ const binfmts = await scanBinfmts(this.scanned, await this.roots(), void 0, sources);
16652
16790
  const manifests = [];
16653
16791
  for (const script of scripts) {
16654
16792
  const head = await this.head(script);
@@ -16683,25 +16821,72 @@ var Launcher = class {
16683
16821
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
16684
16822
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
16685
16823
  }
16686
- module(path) {
16687
- return this.base.stat(path).then((st) => {
16688
- const key = `${path}:${st.size}:${st.mtime.getTime()}`;
16689
- let module = this.compiled.get(key);
16690
- if (!module) {
16691
- module = this.base.readFileBuffer(path).then(async (bytes) => {
16692
- const memory = importedMemory(bytes);
16693
- const foreign = foreignImports(bytes);
16694
- const compiled = await WebAssembly.compile(bytes);
16695
- return {
16696
- module: compiled,
16697
- ...memory ? { memory } : {},
16698
- foreign
16699
- };
16700
- });
16701
- this.compiled.set(key, module);
16702
- module.catch(() => this.compiled.delete(key));
16703
- }
16704
- return module;
16824
+ verifiedFs(fs) {
16825
+ const read = (path) => this.verified(path, KERNEL_CALLER, fs);
16826
+ return Object.assign(Object.create(fs), {
16827
+ readFileBuffer: read,
16828
+ readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
16829
+ });
16830
+ }
16831
+ async cacheKey(path) {
16832
+ const verified = await this.integrity?.key(path);
16833
+ if (verified !== void 0) return verified;
16834
+ const st = await this.base.stat(path);
16835
+ return `${path}:${st.size}:${st.mtime.getTime()}`;
16836
+ }
16837
+ verified(path, caller, fs = this.base) {
16838
+ const plain = () => fs.readFileBuffer(path);
16839
+ return this.integrity ? this.integrity.read(path, caller, plain) : plain();
16840
+ }
16841
+ keyed(path, caller) {
16842
+ const plain = async () => this.base.readFileBuffer(path);
16843
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
16844
+ }
16845
+ cached(cache, key, load) {
16846
+ const hit = cache.get(key);
16847
+ if (hit !== void 0) return hit;
16848
+ const loading2 = load();
16849
+ const found = loading2.then(({ value }) => value);
16850
+ cache.set(key, found);
16851
+ loading2.then(
16852
+ (done4) => {
16853
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
16854
+ if (cache.get(key) === found) cache.delete(key);
16855
+ if (done4.key !== void 0) cache.set(done4.key, found);
16856
+ },
16857
+ () => cache.delete(key)
16858
+ );
16859
+ return found;
16860
+ }
16861
+ async module(path, caller) {
16862
+ const key = await this.cacheKey(path);
16863
+ return this.cached(this.compiled, key, async () => {
16864
+ const { bytes, key: read } = await this.keyed(path, caller);
16865
+ const memory = importedMemory(bytes);
16866
+ const foreign = foreignImports(bytes);
16867
+ const compiled = await WebAssembly.compile(bytes);
16868
+ return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
16869
+ });
16870
+ }
16871
+ async checkedSources(planned, caller) {
16872
+ const { target } = planned;
16873
+ const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
16874
+ const manifests = /* @__PURE__ */ new Set();
16875
+ for (const file of files) {
16876
+ const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
16877
+ if (root !== void 0) manifests.add(`${root}/package.json`);
16878
+ }
16879
+ for (const source of planned.sources ?? []) await this.text(source, caller);
16880
+ for (const manifest of manifests) {
16881
+ if (await this.base.exists(manifest)) await this.text(manifest, caller);
16882
+ }
16883
+ }
16884
+ async text(path, caller) {
16885
+ if (!this.integrity) return this.base.readFile(path);
16886
+ const key = await this.cacheKey(path);
16887
+ return this.cached(this.texts, key, async () => {
16888
+ const { bytes, key: read } = await this.keyed(path, caller);
16889
+ return { value: new TextDecoder().decode(bytes), key: read };
16705
16890
  });
16706
16891
  }
16707
16892
  async names(path, module) {
@@ -16713,23 +16898,23 @@ var Launcher = class {
16713
16898
  const optional = `${root}-names${beside.slice(root.length)}`;
16714
16899
  return await this.base.exists(optional) ? optional : void 0;
16715
16900
  }
16716
- async program(target, env) {
16901
+ async program(target, env, caller) {
16717
16902
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
16718
16903
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
16719
16904
  }
16720
16905
  if (target.abi === "js") {
16721
- return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
16906
+ return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
16722
16907
  }
16723
16908
  if (target.abi !== "wasi") {
16724
16909
  const [glue, { module: module2 }] = await Promise.all([
16725
- this.base.readFile(target.glue),
16726
- this.module(target.wasm)
16910
+ this.text(target.glue, caller),
16911
+ this.module(target.wasm, caller)
16727
16912
  ]);
16728
16913
  return { glue, module: module2 };
16729
16914
  }
16730
- const { module, memory, foreign } = await this.module(target.wasm);
16915
+ const { module, memory, foreign } = await this.module(target.wasm, caller);
16731
16916
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
16732
- const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
16917
+ const imports = target.imports ? await this.text(target.imports, caller) : void 0;
16733
16918
  return {
16734
16919
  abi: "wasi",
16735
16920
  glue: "",
@@ -16751,7 +16936,9 @@ var Launcher = class {
16751
16936
  env.HOSTNAME = this.hostname;
16752
16937
  let program;
16753
16938
  try {
16754
- program = await this.program(target, env);
16939
+ const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tag };
16940
+ if (this.integrity) await this.checkedSources(planned, caller);
16941
+ program = await this.program(target, env, caller);
16755
16942
  } catch (err) {
16756
16943
  await req.fds.closeAll();
16757
16944
  throw err;
@@ -17243,11 +17430,14 @@ var Launcher = class {
17243
17430
  }
17244
17431
  };
17245
17432
  }
17246
- const module = (await scanFilesystems(this.base, await this.roots())).get(type);
17433
+ const module = (await scanFilesystems(this.scanned, await this.roots())).get(type);
17247
17434
  if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
17248
17435
  if (!this.createDriverWorker)
17249
17436
  throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
17250
- const code = await this.base.readFile(module);
17437
+ const root = PACKAGE_ROOT.exec(module)?.[1];
17438
+ if (this.integrity && root !== void 0)
17439
+ await this.text(`${root}/package.json`, KERNEL_CALLER);
17440
+ const code = new TextDecoder().decode(await this.verified(module, KERNEL_CALLER));
17251
17441
  const worker = this.createDriverWorker();
17252
17442
  const driver = new MessageChannel();
17253
17443
  const client = new MessageChannel();