@ai-ecoverse/slicc-kernel 1.63.0 → 1.64.1
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 +2 -1
- package/dist/kernel-worker.js +101 -13
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +22 -2
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +101 -13
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +22 -2
- package/dist/process-worker.js.map +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -560,7 +560,8 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
560
560
|
- **Opening a file:** an open needs `r` to read and `w` to write or truncate, checked once when the file is opened, in every runtime: Emscripten, WASI (a second open of a path the program already has open is checked as well), JS programs' `ctx.open` and `ctx.fs.writeFile`, and the kernel's own opens. A file the open itself creates is not checked (only its directory is), so `open(O_CREAT | O_WRONLY, 0444)` works. An open descriptor keeps the access it was opened with, through a later `chmod`, and so do descriptors a child inherits. Reads and writes without a descriptor (`ctx.fs.readFile`, `ctx.sync.fs.readFile`) are checked each time.
|
|
561
561
|
- **`access(2)`:** `faccessat` (bash's `[ -r ]`, `test -w`) answers from the same checks, with the real uid and gid, or the effective ones with `AT_EACCESS`, for Emscripten programs, and for WASI programs through `slicc_fs.path_access` (see [File modes and the umask](#file-modes-and-the-umask)).
|
|
562
562
|
- **Exec:** running a program by path needs `x` on it, and `r` too when it's a script (`#!` or a `binfmt` suffix); a WASI or Emscripten binary needs only `x`. Root needs an `x` bit as well. Commands found by name, and `/usr/bin` and `/bin`, are always executable; a user's own commands in `~/.local/share/pnpm/bin` are reached through the user's home, so another user gets `EACCES` there, as for any file. A refused exec is `EACCES` (126 in bash).
|
|
563
|
-
-
|
|
563
|
+
- **The layout:** with `users: 'report'` or `'enforce'`, the kernel gives its own paths an owner and a mode when they have no owner record yet: on a fresh root, and once on a root from before owners. These are `/` `0755`, `/root` `0700`, `/home` `0755`, `/etc` `0755`, `/etc/passwd` and `/etc/group` `0644`, `/usr` and `/usr/local` `0755`, `/node_modules` (or the `modules` option) and the system `PNPM_HOME` `0755`, all root's. A path that has a record keeps it, so an admin's `chmod 711 /home` or `chown cone /node_modules` lasts across restarts; a mounted path is left to its mount, and a path below a symlinked directory is stamped where it really is. Only a root that records owners is stamped: the OPFS root with its sidecar, which the page and the Node entry (in memory) both use; a custom root file system without owner records is left as it is. The directory for files removed while open, `/.slicc-unlinked`, is made root's `0700`. `/tmp` is the exception: it is set to `1777` at every start. Emscripten programs see `/` at `0755`, `/dev` at `0755`, `null`, `zero`, `full`, `random`, `urandom` and `tty` at `0666`, and `/dev/shm` at `1777`. With `users: 'off'` nothing is stamped. Paths the embedder makes (bios's `/opt` and `/var/lib/slicc`) are the embedder's to set, and `/etc/sudoers` comes with `sudo`.
|
|
564
|
+
- **Terminals:** a terminal from `openTerminal` belongs to its session's user, with group `tty` when `/etc/group` has one (the user's own group otherwise), mode `0620`; a pseudo-terminal's slave `/dev/pts/N` belongs to the uid and gid that opened `/dev/ptmx`, mode `0620`. Opening one by name (`/dev/ttyN`, `/dev/pts/N`, from every runtime) needs both `r` and `w`, so only its owner and root open it: the kernel's terminal descriptor always reads and writes, so the group's `w` bit (`write`, `wall` on Linux) does not let a group member open it. `/dev/tty` is the opener's own controlling terminal and is not checked, and descriptors a process inherited (across `sudo` or a `setresuid` too) keep working. Emscripten programs' `stat` of `/dev/ttyN` and `/dev/pts/N` shows the owner and mode. With `users: 'off'` terminals have no owner, as before.
|
|
564
565
|
- **A client's `fs`:** checked as the client's user; a page's client (root) passes.
|
|
565
566
|
|
|
566
567
|
With `users: 'enforce'` not yet the default, no release turns it on before `sudo` exists.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -427,6 +427,9 @@ var OpfsFs = class {
|
|
|
427
427
|
trackOwners() {
|
|
428
428
|
this.owners = true;
|
|
429
429
|
}
|
|
430
|
+
async hasOwner(path) {
|
|
431
|
+
return ownOf((await this.meta.get([path]))[0]) !== void 0;
|
|
432
|
+
}
|
|
430
433
|
async ancestry(path) {
|
|
431
434
|
const entries = await this.meta.get(chain(path));
|
|
432
435
|
const out = [];
|
|
@@ -1481,6 +1484,11 @@ var Created = class {
|
|
|
1481
1484
|
function deny(g, op, path, errno = "EACCES") {
|
|
1482
1485
|
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
|
|
1483
1486
|
}
|
|
1487
|
+
var TTY_MODE = 400;
|
|
1488
|
+
function ttyOpenable(g, tty) {
|
|
1489
|
+
if (!g || !tty.owner || permits(g.cred, tty.owner, R_OK | W_OK, false)) return;
|
|
1490
|
+
deny(g, "open", tty.name ?? "/dev/tty");
|
|
1491
|
+
}
|
|
1484
1492
|
async function need(g, op, path, want, options = {}, shown = path) {
|
|
1485
1493
|
const walked = await access(g.fs, g.cred, path, want, options);
|
|
1486
1494
|
if (walked.denied !== void 0) deny(g, op, shown);
|
|
@@ -3193,10 +3201,18 @@ var UserDb = class {
|
|
|
3193
3201
|
};
|
|
3194
3202
|
}
|
|
3195
3203
|
async lookup(user) {
|
|
3204
|
+
return (await this.lookupTerminal(user))?.account;
|
|
3205
|
+
}
|
|
3206
|
+
async ttyGroup() {
|
|
3207
|
+
return parsed(parseGroup(await this.read(GROUP, DEFAULT_GROUP))).find((g) => g.name === "tty")?.gid;
|
|
3208
|
+
}
|
|
3209
|
+
async lookupTerminal(user) {
|
|
3196
3210
|
const { passwd, group } = await this.tables();
|
|
3197
3211
|
const key = typeof user === "number" ? "uid" : "name";
|
|
3198
3212
|
const row = parsed(passwd).find((r) => r[key] === user);
|
|
3199
|
-
|
|
3213
|
+
const groups = parsed(group);
|
|
3214
|
+
const tty = groups.find((g) => g.name === "tty")?.gid;
|
|
3215
|
+
return row && { account: this.account(row, groups), tty };
|
|
3200
3216
|
}
|
|
3201
3217
|
async list() {
|
|
3202
3218
|
const { passwd, group } = await this.tables();
|
|
@@ -6427,6 +6443,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6427
6443
|
...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
|
|
6428
6444
|
...fs.chown ? { chown: fs.chown.bind(fs) } : {},
|
|
6429
6445
|
...fs.ancestry ? { ancestry: fs.ancestry.bind(fs) } : {},
|
|
6446
|
+
...fs.hasOwner ? { hasOwner: fs.hasOwner.bind(fs) } : {},
|
|
6430
6447
|
chmod: (path, mode) => fs.chmod(path, mode),
|
|
6431
6448
|
utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
|
|
6432
6449
|
lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
|
|
@@ -6526,8 +6543,10 @@ var UnlinkedKeeper = class {
|
|
|
6526
6543
|
count = 0;
|
|
6527
6544
|
links = /* @__PURE__ */ new Map();
|
|
6528
6545
|
linked = /* @__PURE__ */ new Map();
|
|
6546
|
+
dirOwner;
|
|
6529
6547
|
constructor(fs, holders, rootOf, options = {}) {
|
|
6530
6548
|
const { locks, owner } = options;
|
|
6549
|
+
this.dirOwner = options.dirOwner;
|
|
6531
6550
|
this.fs = fs;
|
|
6532
6551
|
this.holders = holders;
|
|
6533
6552
|
this.rootOf = rootOf;
|
|
@@ -6638,7 +6657,8 @@ var UnlinkedKeeper = class {
|
|
|
6638
6657
|
const dir = dirOf(this.rootOf(path));
|
|
6639
6658
|
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
6640
6659
|
try {
|
|
6641
|
-
|
|
6660
|
+
const owner = this.dirOwner;
|
|
6661
|
+
await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
|
|
6642
6662
|
await this.fs.rename(path, hidden);
|
|
6643
6663
|
await this.linkedName(hidden);
|
|
6644
6664
|
return hidden;
|
|
@@ -8017,6 +8037,10 @@ var MountFs = class {
|
|
|
8017
8037
|
if (this.at(real)) throw errnoError("ENOSYS", path);
|
|
8018
8038
|
return real;
|
|
8019
8039
|
}
|
|
8040
|
+
async hasOwner(path) {
|
|
8041
|
+
if (this.at(path)) return true;
|
|
8042
|
+
return await this.base.hasOwner?.(path) ?? true;
|
|
8043
|
+
}
|
|
8020
8044
|
async ancestry(path) {
|
|
8021
8045
|
const names = chain(normalizePath(path));
|
|
8022
8046
|
const first = names.findIndex((name2) => this.at(name2));
|
|
@@ -9805,6 +9829,7 @@ var KernelTty = class {
|
|
|
9805
9829
|
waiters = [];
|
|
9806
9830
|
hungUp = false;
|
|
9807
9831
|
name;
|
|
9832
|
+
owner;
|
|
9808
9833
|
onRead;
|
|
9809
9834
|
readers = 0;
|
|
9810
9835
|
screen;
|
|
@@ -10148,10 +10173,11 @@ var PtyTable = class {
|
|
|
10148
10173
|
this.signal = signal;
|
|
10149
10174
|
this.onRead = onRead;
|
|
10150
10175
|
}
|
|
10151
|
-
open() {
|
|
10176
|
+
open(owner) {
|
|
10152
10177
|
let index = 0;
|
|
10153
10178
|
while (this.pairs.has(index)) index += 1;
|
|
10154
10179
|
const pair = new PtyPair(index, this.signal, (p) => this.pairs.delete(p.index), this.onRead);
|
|
10180
|
+
if (owner) pair.slave.owner = { uid: owner.uid, gid: owner.gid, mode: TTY_MODE };
|
|
10155
10181
|
this.pairs.set(index, pair);
|
|
10156
10182
|
return { pair, master: pair.master() };
|
|
10157
10183
|
}
|
|
@@ -10164,6 +10190,10 @@ var PtyTable = class {
|
|
|
10164
10190
|
return [...this.pairs.keys()].sort((a, b) => a - b);
|
|
10165
10191
|
}
|
|
10166
10192
|
};
|
|
10193
|
+
function ptsNumber(path) {
|
|
10194
|
+
const m = /^\/dev\/pts\/(\d+)$/.exec(path);
|
|
10195
|
+
return m ? Number(m[1]) : void 0;
|
|
10196
|
+
}
|
|
10167
10197
|
var PTY_OPS = [
|
|
10168
10198
|
"pty-open",
|
|
10169
10199
|
"pty-slave-open",
|
|
@@ -10191,11 +10221,12 @@ function ptySyscall(req, ctx) {
|
|
|
10191
10221
|
switch (req.op) {
|
|
10192
10222
|
case "pty-open": {
|
|
10193
10223
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
10194
|
-
return json4(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
10224
|
+
return json4(ctx.fds.install(ctx.ptys.open(ctx.owner).master, 3));
|
|
10195
10225
|
}
|
|
10196
10226
|
case "pty-slave-open": {
|
|
10197
10227
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
10198
10228
|
const pair = ctx.ptys.get(req.n);
|
|
10229
|
+
ttyOpenable(ctx.guard, pair.slave);
|
|
10199
10230
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
10200
10231
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
10201
10232
|
return json4(fd);
|
|
@@ -11011,6 +11042,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11011
11042
|
"fd-promote",
|
|
11012
11043
|
"fd-open-tty",
|
|
11013
11044
|
"fd-tty-names",
|
|
11045
|
+
"fd-tty-owner",
|
|
11014
11046
|
"tty-get",
|
|
11015
11047
|
"tty-set",
|
|
11016
11048
|
"tty-winsz",
|
|
@@ -11212,7 +11244,9 @@ var WasmProcess = class {
|
|
|
11212
11244
|
}
|
|
11213
11245
|
if (isPtySyscall(req)) {
|
|
11214
11246
|
const { ptys, jobs } = this.options;
|
|
11215
|
-
|
|
11247
|
+
const owner = this.options.owners ? this.ttyOwner() : void 0;
|
|
11248
|
+
const guard2 = this.guarded();
|
|
11249
|
+
return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner });
|
|
11216
11250
|
}
|
|
11217
11251
|
return await this.procSyscall(req, cancelled);
|
|
11218
11252
|
} catch (e) {
|
|
@@ -11344,6 +11378,25 @@ var WasmProcess = class {
|
|
|
11344
11378
|
const want = (access2 === 1 ? 0 : R_OK) | (access2 !== 0 || req.truncate ? W_OK : 0);
|
|
11345
11379
|
await openable(g, "open", req.path, want, req.create === true);
|
|
11346
11380
|
}
|
|
11381
|
+
openTty(name2) {
|
|
11382
|
+
const tty = name2 === void 0 ? this.controllingTerminal() : this.options.jobs?.terminalNamed(name2);
|
|
11383
|
+
if (!tty) throw new KernelError("ENXIO");
|
|
11384
|
+
if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
|
|
11385
|
+
return this.fds.install(tty.file(), 3);
|
|
11386
|
+
}
|
|
11387
|
+
guarded() {
|
|
11388
|
+
const { guard: guard2 } = this.options;
|
|
11389
|
+
return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
|
|
11390
|
+
}
|
|
11391
|
+
ttyOwner() {
|
|
11392
|
+
return { uid: this.cred.euid, gid: this.cred.egid, mode: TTY_MODE };
|
|
11393
|
+
}
|
|
11394
|
+
terminalByName(name2) {
|
|
11395
|
+
const n = ptsNumber(name2);
|
|
11396
|
+
const tty = n === void 0 ? this.options.jobs?.terminalNamed(name2) : this.options.ptys?.get(n).slave;
|
|
11397
|
+
if (!tty) throw new KernelError("ENOENT");
|
|
11398
|
+
return tty;
|
|
11399
|
+
}
|
|
11347
11400
|
newOwner() {
|
|
11348
11401
|
if (!this.options.owners) return void 0;
|
|
11349
11402
|
return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
|
|
@@ -11447,13 +11500,12 @@ var WasmProcess = class {
|
|
|
11447
11500
|
if (!req.keep) await Promise.resolve(this.fds.close(req.from));
|
|
11448
11501
|
}
|
|
11449
11502
|
return { ok: true, kind: "void" };
|
|
11450
|
-
case "fd-open-tty":
|
|
11451
|
-
|
|
11452
|
-
if (!tty) throw new KernelError("ENXIO");
|
|
11453
|
-
return { ok: true, kind: "json", json: this.fds.install(tty.file(), 3) };
|
|
11454
|
-
}
|
|
11503
|
+
case "fd-open-tty":
|
|
11504
|
+
return { ok: true, kind: "json", json: this.openTty(req.name) };
|
|
11455
11505
|
case "fd-tty-names":
|
|
11456
11506
|
return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
|
|
11507
|
+
case "fd-tty-owner":
|
|
11508
|
+
return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
|
|
11457
11509
|
case "fd-select": {
|
|
11458
11510
|
const { read, write, timeoutMs } = req;
|
|
11459
11511
|
if (this.owed && timeoutMs !== 0) {
|
|
@@ -14126,6 +14178,16 @@ var NOT_FOUND = 127;
|
|
|
14126
14178
|
var DECIDE_MS = 100;
|
|
14127
14179
|
var INIT_PID = 1;
|
|
14128
14180
|
var SHARED_DIRS = ["/tmp", "/home"];
|
|
14181
|
+
var LAYOUT = [
|
|
14182
|
+
["/", 493],
|
|
14183
|
+
["/root", 448],
|
|
14184
|
+
["/home", 493],
|
|
14185
|
+
["/etc", 493],
|
|
14186
|
+
[PASSWD, 420],
|
|
14187
|
+
[GROUP, 420],
|
|
14188
|
+
["/usr", 493],
|
|
14189
|
+
["/usr/local", 493]
|
|
14190
|
+
];
|
|
14129
14191
|
var encoder3 = new TextEncoder();
|
|
14130
14192
|
var USERS_MODES = ["off", "report", "enforce"];
|
|
14131
14193
|
function usersMode(mode = "off") {
|
|
@@ -14319,7 +14381,8 @@ var Launcher = class {
|
|
|
14319
14381
|
{
|
|
14320
14382
|
locks: webLocks(),
|
|
14321
14383
|
busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
|
|
14322
|
-
owner: options.unlinkOwner
|
|
14384
|
+
owner: options.unlinkOwner,
|
|
14385
|
+
...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
|
|
14323
14386
|
}
|
|
14324
14387
|
);
|
|
14325
14388
|
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
@@ -14944,6 +15007,24 @@ var Launcher = class {
|
|
|
14944
15007
|
await this.provisionPnpmHome();
|
|
14945
15008
|
return entry;
|
|
14946
15009
|
}
|
|
15010
|
+
async stampLayout() {
|
|
15011
|
+
const layout = [
|
|
15012
|
+
...LAYOUT,
|
|
15013
|
+
[this.modulesDir, 493],
|
|
15014
|
+
[this.pnpmHome, 493],
|
|
15015
|
+
[`/${UNLINKED_DIR}`, 448]
|
|
15016
|
+
];
|
|
15017
|
+
for (const [path, mode] of layout) {
|
|
15018
|
+
const real = await this.realLayoutPath(path);
|
|
15019
|
+
const fresh = real !== void 0 && !await this.base.hasOwner?.(real);
|
|
15020
|
+
if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
|
|
15021
|
+
}
|
|
15022
|
+
}
|
|
15023
|
+
async realLayoutPath(path) {
|
|
15024
|
+
if (!await this.base.exists(path)) return void 0;
|
|
15025
|
+
if (path === "/") return path;
|
|
15026
|
+
return joinName(await realDir(this.base, parentOf3(path)), baseName(path));
|
|
15027
|
+
}
|
|
14947
15028
|
async provisionPnpmHome() {
|
|
14948
15029
|
await this.base.mkdir(this.pnpmHome, { recursive: true }).catch(() => void 0);
|
|
14949
15030
|
}
|
|
@@ -15017,6 +15098,7 @@ var Launcher = class {
|
|
|
15017
15098
|
if (this.owners) await this.base.chmod("/tmp", 1023);
|
|
15018
15099
|
await this.provisionPnpmHome();
|
|
15019
15100
|
await this.users.ensure();
|
|
15101
|
+
if (this.owners) await this.stampLayout();
|
|
15020
15102
|
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
15021
15103
|
this.fstab = this.mountFstab();
|
|
15022
15104
|
}
|
|
@@ -15045,7 +15127,12 @@ var Launcher = class {
|
|
|
15045
15127
|
}
|
|
15046
15128
|
async account(user) {
|
|
15047
15129
|
if (typeof user === "object") return user;
|
|
15048
|
-
|
|
15130
|
+
return (await this.terminalAccount(user)).account;
|
|
15131
|
+
}
|
|
15132
|
+
async terminalAccount(user) {
|
|
15133
|
+
if (typeof user === "object")
|
|
15134
|
+
return { account: user, tty: this.owners ? await this.users.ttyGroup() : void 0 };
|
|
15135
|
+
const found = await this.users.lookupTerminal(user ?? 0);
|
|
15049
15136
|
if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
|
|
15050
15137
|
return found;
|
|
15051
15138
|
}
|
|
@@ -15067,7 +15154,7 @@ var Launcher = class {
|
|
|
15067
15154
|
return { cwd, file, planned };
|
|
15068
15155
|
}
|
|
15069
15156
|
async openTerminal(argv, options) {
|
|
15070
|
-
const account = await this.
|
|
15157
|
+
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
15071
15158
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15072
15159
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
15073
15160
|
let leader = 0;
|
|
@@ -15077,6 +15164,7 @@ var Launcher = class {
|
|
|
15077
15164
|
);
|
|
15078
15165
|
tty.onRead = () => this.settling.reading(this.jobs.tcgetpgrp(tty, leader));
|
|
15079
15166
|
tty.name = `/dev/tty${++this.terminals}`;
|
|
15167
|
+
if (this.owners) tty.owner = { uid: account.uid, gid: ttyGroup ?? account.gid, mode: TTY_MODE };
|
|
15080
15168
|
tty.setSize(options.cols ?? 80, options.rows ?? 24);
|
|
15081
15169
|
const fds = new FdTable();
|
|
15082
15170
|
const stdio = tty.file();
|