@ai-ecoverse/slicc-kernel 1.63.0 → 1.64.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 CHANGED
@@ -560,7 +560,7 @@ 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
- - **`/tmp` and `/dev`:** with `users: 'report'` or `'enforce'`, `/tmp` is `1777` (set again at every start), and Emscripten programs see `/dev` at `0755`, `null`, `zero`, `full`, `random`, `urandom` and `tty` at `0666`, and `/dev/shm` at `1777`. With `users: 'off'` they keep their modes as before.
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
564
  - **A client's `fs`:** checked as the client's user; a page's client (root) passes.
565
565
 
566
566
  With `users: 'enforce'` not yet the default, no release turns it on before `sudo` exists.
@@ -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 = [];
@@ -6427,6 +6430,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6427
6430
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
6428
6431
  ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
6429
6432
  ...fs.ancestry ? { ancestry: fs.ancestry.bind(fs) } : {},
6433
+ ...fs.hasOwner ? { hasOwner: fs.hasOwner.bind(fs) } : {},
6430
6434
  chmod: (path, mode) => fs.chmod(path, mode),
6431
6435
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
6432
6436
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -6526,8 +6530,10 @@ var UnlinkedKeeper = class {
6526
6530
  count = 0;
6527
6531
  links = /* @__PURE__ */ new Map();
6528
6532
  linked = /* @__PURE__ */ new Map();
6533
+ dirOwner;
6529
6534
  constructor(fs, holders, rootOf, options = {}) {
6530
6535
  const { locks, owner } = options;
6536
+ this.dirOwner = options.dirOwner;
6531
6537
  this.fs = fs;
6532
6538
  this.holders = holders;
6533
6539
  this.rootOf = rootOf;
@@ -6638,7 +6644,8 @@ var UnlinkedKeeper = class {
6638
6644
  const dir = dirOf(this.rootOf(path));
6639
6645
  const hidden = `${dir}/${this.instance}.${++this.count}`;
6640
6646
  try {
6641
- await this.fs.mkdir(dir, { recursive: true });
6647
+ const owner = this.dirOwner;
6648
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
6642
6649
  await this.fs.rename(path, hidden);
6643
6650
  await this.linkedName(hidden);
6644
6651
  return hidden;
@@ -8017,6 +8024,10 @@ var MountFs = class {
8017
8024
  if (this.at(real)) throw errnoError("ENOSYS", path);
8018
8025
  return real;
8019
8026
  }
8027
+ async hasOwner(path) {
8028
+ if (this.at(path)) return true;
8029
+ return await this.base.hasOwner?.(path) ?? true;
8030
+ }
8020
8031
  async ancestry(path) {
8021
8032
  const names = chain(normalizePath(path));
8022
8033
  const first = names.findIndex((name2) => this.at(name2));
@@ -14126,6 +14137,16 @@ var NOT_FOUND = 127;
14126
14137
  var DECIDE_MS = 100;
14127
14138
  var INIT_PID = 1;
14128
14139
  var SHARED_DIRS = ["/tmp", "/home"];
14140
+ var LAYOUT = [
14141
+ ["/", 493],
14142
+ ["/root", 448],
14143
+ ["/home", 493],
14144
+ ["/etc", 493],
14145
+ [PASSWD, 420],
14146
+ [GROUP, 420],
14147
+ ["/usr", 493],
14148
+ ["/usr/local", 493]
14149
+ ];
14129
14150
  var encoder3 = new TextEncoder();
14130
14151
  var USERS_MODES = ["off", "report", "enforce"];
14131
14152
  function usersMode(mode = "off") {
@@ -14319,7 +14340,8 @@ var Launcher = class {
14319
14340
  {
14320
14341
  locks: webLocks(),
14321
14342
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
14322
- owner: options.unlinkOwner
14343
+ owner: options.unlinkOwner,
14344
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
14323
14345
  }
14324
14346
  );
14325
14347
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
@@ -14944,6 +14966,24 @@ var Launcher = class {
14944
14966
  await this.provisionPnpmHome();
14945
14967
  return entry;
14946
14968
  }
14969
+ async stampLayout() {
14970
+ const layout = [
14971
+ ...LAYOUT,
14972
+ [this.modulesDir, 493],
14973
+ [this.pnpmHome, 493],
14974
+ [`/${UNLINKED_DIR}`, 448]
14975
+ ];
14976
+ for (const [path, mode] of layout) {
14977
+ const real = await this.realLayoutPath(path);
14978
+ const fresh = real !== void 0 && !await this.base.hasOwner?.(real);
14979
+ if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
14980
+ }
14981
+ }
14982
+ async realLayoutPath(path) {
14983
+ if (!await this.base.exists(path)) return void 0;
14984
+ if (path === "/") return path;
14985
+ return joinName(await realDir(this.base, parentOf3(path)), baseName(path));
14986
+ }
14947
14987
  async provisionPnpmHome() {
14948
14988
  await this.base.mkdir(this.pnpmHome, { recursive: true }).catch(() => void 0);
14949
14989
  }
@@ -15017,6 +15057,7 @@ var Launcher = class {
15017
15057
  if (this.owners) await this.base.chmod("/tmp", 1023);
15018
15058
  await this.provisionPnpmHome();
15019
15059
  await this.users.ensure();
15060
+ if (this.owners) await this.stampLayout();
15020
15061
  await writeCaFile(this.base, this.ca).catch(() => void 0);
15021
15062
  this.fstab = this.mountFstab();
15022
15063
  }