@ai-ecoverse/slicc-kernel 1.81.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/README.md +7 -0
- package/dist/index.js +44 -3
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +263 -41
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +226 -37
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/dist/node.js
CHANGED
|
@@ -3033,6 +3033,97 @@ var MemoryMeta = class {
|
|
|
3033
3033
|
}
|
|
3034
3034
|
};
|
|
3035
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
|
+
|
|
3036
3127
|
// src/fs/protect.ts
|
|
3037
3128
|
function writerTags(protection) {
|
|
3038
3129
|
const writers = protection?.writers ?? [];
|
|
@@ -3885,6 +3976,29 @@ var OpfsFs = class {
|
|
|
3885
3976
|
false
|
|
3886
3977
|
);
|
|
3887
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
|
+
}
|
|
3888
4002
|
async pread(path, offset, length2) {
|
|
3889
4003
|
const handle = await this.existing(path);
|
|
3890
4004
|
return this.retrying(
|
|
@@ -5539,7 +5653,7 @@ function owned(path, st) {
|
|
|
5539
5653
|
function unchanged(a, b, bytes) {
|
|
5540
5654
|
return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
|
|
5541
5655
|
}
|
|
5542
|
-
async function
|
|
5656
|
+
async function sha2562(bytes) {
|
|
5543
5657
|
const digest = new Uint8Array(
|
|
5544
5658
|
await crypto.subtle.digest("SHA-256", bytes)
|
|
5545
5659
|
);
|
|
@@ -5595,7 +5709,7 @@ async function attest(fs, asked, o) {
|
|
|
5595
5709
|
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
5596
5710
|
const after = await fs.lstat(path);
|
|
5597
5711
|
if (unchanged(before, after, bytes)) {
|
|
5598
|
-
return attested(path, after, bytes, await
|
|
5712
|
+
return attested(path, after, bytes, await sha2562(bytes), o, parents);
|
|
5599
5713
|
}
|
|
5600
5714
|
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
5601
5715
|
}
|
|
@@ -16295,6 +16409,13 @@ var Launcher = class {
|
|
|
16295
16409
|
unwatchScopes = () => void 0;
|
|
16296
16410
|
warned = /* @__PURE__ */ new Set();
|
|
16297
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
|
+
}
|
|
16298
16419
|
processes = /* @__PURE__ */ new Map();
|
|
16299
16420
|
zombies = /* @__PURE__ */ new Set();
|
|
16300
16421
|
orphans = /* @__PURE__ */ new Set();
|
|
@@ -16398,7 +16519,7 @@ var Launcher = class {
|
|
|
16398
16519
|
if (options.uplink) this.net.useUplink({ uplink: options.uplink, routes: this.routes });
|
|
16399
16520
|
enableNetwork(this.net, {
|
|
16400
16521
|
transport: this.transport,
|
|
16401
|
-
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
16522
|
+
engine: kernelTlsEngine(packageTlsEngine(this.verifiedFs(options.fs), this.modulesDir)),
|
|
16402
16523
|
ca: this.ca
|
|
16403
16524
|
});
|
|
16404
16525
|
enableCdp(this.net, this.cdp);
|
|
@@ -16433,6 +16554,16 @@ var Launcher = class {
|
|
|
16433
16554
|
this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
|
|
16434
16555
|
this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
|
|
16435
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
|
+
}
|
|
16436
16567
|
this.users = new UserDb(this.fs, this.owners);
|
|
16437
16568
|
this.sudoer = new Sudo({
|
|
16438
16569
|
fs: this.fs,
|
|
@@ -16478,7 +16609,7 @@ var Launcher = class {
|
|
|
16478
16609
|
commands(scope = "") {
|
|
16479
16610
|
let found = this.catalogs.get(scope);
|
|
16480
16611
|
if (!found) {
|
|
16481
|
-
found = this.roots(scope).then((roots) => scanCommands(this.
|
|
16612
|
+
found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
|
|
16482
16613
|
this.catalogs.set(scope, found);
|
|
16483
16614
|
}
|
|
16484
16615
|
return found;
|
|
@@ -16486,7 +16617,7 @@ var Launcher = class {
|
|
|
16486
16617
|
ownCommands(scope) {
|
|
16487
16618
|
let found = this.ownCatalogs.get(scope);
|
|
16488
16619
|
if (!found) {
|
|
16489
|
-
found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.
|
|
16620
|
+
found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => new Set(commands.keys()));
|
|
16490
16621
|
this.ownCatalogs.set(scope, found);
|
|
16491
16622
|
}
|
|
16492
16623
|
return found;
|
|
@@ -16495,7 +16626,7 @@ var Launcher = class {
|
|
|
16495
16626
|
let found = this.binfmts.get(scope);
|
|
16496
16627
|
if (!found) {
|
|
16497
16628
|
found = this.roots(scope).then(
|
|
16498
|
-
(roots) => scanBinfmts(this.
|
|
16629
|
+
(roots) => scanBinfmts(this.scanned, roots, (message3) => {
|
|
16499
16630
|
if (this.warned.has(message3)) return;
|
|
16500
16631
|
this.warned.add(message3);
|
|
16501
16632
|
console.warn(message3);
|
|
@@ -16614,10 +16745,16 @@ var Launcher = class {
|
|
|
16614
16745
|
}
|
|
16615
16746
|
const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
|
|
16616
16747
|
const via = [interp, ...inner?.via ?? []];
|
|
16617
|
-
|
|
16748
|
+
const sources = [script, ...inner?.sources ?? []];
|
|
16749
|
+
return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
|
|
16618
16750
|
}
|
|
16619
16751
|
const target = withScriptEnv(found, command);
|
|
16620
|
-
return {
|
|
16752
|
+
return {
|
|
16753
|
+
target,
|
|
16754
|
+
args: [...target.prefix ?? [], ...passed],
|
|
16755
|
+
via: [interp],
|
|
16756
|
+
sources: [script]
|
|
16757
|
+
};
|
|
16621
16758
|
}
|
|
16622
16759
|
async plan(file, argv, cwd, scope, argv0) {
|
|
16623
16760
|
const found = await this.resolve(file, argv[0] ?? file, cwd, scope);
|
|
@@ -16649,7 +16786,7 @@ var Launcher = class {
|
|
|
16649
16786
|
}
|
|
16650
16787
|
async binfmtManifests(scripts) {
|
|
16651
16788
|
const sources = /* @__PURE__ */ new Map();
|
|
16652
|
-
const binfmts = await scanBinfmts(this.
|
|
16789
|
+
const binfmts = await scanBinfmts(this.scanned, await this.roots(), void 0, sources);
|
|
16653
16790
|
const manifests = [];
|
|
16654
16791
|
for (const script of scripts) {
|
|
16655
16792
|
const head = await this.head(script);
|
|
@@ -16684,25 +16821,72 @@ var Launcher = class {
|
|
|
16684
16821
|
if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
|
|
16685
16822
|
return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
|
|
16686
16823
|
}
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
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 };
|
|
16706
16890
|
});
|
|
16707
16891
|
}
|
|
16708
16892
|
async names(path, module) {
|
|
@@ -16714,23 +16898,23 @@ var Launcher = class {
|
|
|
16714
16898
|
const optional = `${root}-names${beside.slice(root.length)}`;
|
|
16715
16899
|
return await this.base.exists(optional) ? optional : void 0;
|
|
16716
16900
|
}
|
|
16717
|
-
async program(target, env) {
|
|
16901
|
+
async program(target, env, caller) {
|
|
16718
16902
|
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
16719
16903
|
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
16720
16904
|
}
|
|
16721
16905
|
if (target.abi === "js") {
|
|
16722
|
-
return { abi: "js", glue: await this.
|
|
16906
|
+
return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
|
|
16723
16907
|
}
|
|
16724
16908
|
if (target.abi !== "wasi") {
|
|
16725
16909
|
const [glue, { module: module2 }] = await Promise.all([
|
|
16726
|
-
this.
|
|
16727
|
-
this.module(target.wasm)
|
|
16910
|
+
this.text(target.glue, caller),
|
|
16911
|
+
this.module(target.wasm, caller)
|
|
16728
16912
|
]);
|
|
16729
16913
|
return { glue, module: module2 };
|
|
16730
16914
|
}
|
|
16731
|
-
const { module, memory, foreign } = await this.module(target.wasm);
|
|
16915
|
+
const { module, memory, foreign } = await this.module(target.wasm, caller);
|
|
16732
16916
|
const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
|
|
16733
|
-
const imports = target.imports ? await this.
|
|
16917
|
+
const imports = target.imports ? await this.text(target.imports, caller) : void 0;
|
|
16734
16918
|
return {
|
|
16735
16919
|
abi: "wasi",
|
|
16736
16920
|
glue: "",
|
|
@@ -16752,7 +16936,9 @@ var Launcher = class {
|
|
|
16752
16936
|
env.HOSTNAME = this.hostname;
|
|
16753
16937
|
let program;
|
|
16754
16938
|
try {
|
|
16755
|
-
|
|
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);
|
|
16756
16942
|
} catch (err) {
|
|
16757
16943
|
await req.fds.closeAll();
|
|
16758
16944
|
throw err;
|
|
@@ -17244,11 +17430,14 @@ var Launcher = class {
|
|
|
17244
17430
|
}
|
|
17245
17431
|
};
|
|
17246
17432
|
}
|
|
17247
|
-
const module = (await scanFilesystems(this.
|
|
17433
|
+
const module = (await scanFilesystems(this.scanned, await this.roots())).get(type);
|
|
17248
17434
|
if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
|
|
17249
17435
|
if (!this.createDriverWorker)
|
|
17250
17436
|
throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
|
|
17251
|
-
const
|
|
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));
|
|
17252
17441
|
const worker = this.createDriverWorker();
|
|
17253
17442
|
const driver = new MessageChannel();
|
|
17254
17443
|
const client = new MessageChannel();
|