From a6d2f6dca72d6b1c7f6f933b5a520f64a2149a84 Mon Sep 17 00:00:00 2001 From: spsf Date: Mon, 23 Feb 2026 23:05:13 -0600 Subject: [PATCH] /home/user owned by user, user starts in cwd /home/user --- Src/Hyperion-bash/bin/chgrp | 10 +- Src/Hyperion-bash/bin/chmod | 8 +- Src/Hyperion-bash/bin/chown | 10 +- Src/Hyperion-bash/bin/hysh | 8 +- Src/Hyperion-bash/bin/login | 3 + Src/Hyperion-core/sbin/init.lua | 3 + .../lib/modules/Hyperion/10_vfs.kmod | 54 ++++++- .../lib/modules/Hyperion/30_userspace.kmod | 4 +- .../lib/modules/Hyperion/40_auth.kmod | 2 + .../lib/modules/Hyperion/92_permissions.kmod | 138 +++++++++++++----- .../lib/modules/Hyperion/99_final.kmod | 5 + 11 files changed, 199 insertions(+), 46 deletions(-) create mode 100644 Src/Hyperion-kernel/lib/modules/Hyperion/99_final.kmod diff --git a/Src/Hyperion-bash/bin/chgrp b/Src/Hyperion-bash/bin/chgrp index 51508f8..241af61 100644 --- a/Src/Hyperion-bash/bin/chgrp +++ b/Src/Hyperion-bash/bin/chgrp @@ -64,12 +64,18 @@ local newGid = resolveGid(groupStr) local function chgrpPath(path) local stat = syscall.stat(path) if not stat then - print(name .. ": cannot stat '" .. path .. "': No such file or directory") + print(name .. ": cannot stat '" .. path .. "': no such file or directory") return false end local ok, err = pcall(syscall.chown, path, stat.owner, newGid) if not ok then - print(name .. ": cannot change group of '" .. path .. "': " .. tostring(err)) + local msg = tostring(err) + if msg:find("EPERM") or msg:find("EACCES") then + msg = "operation not permitted (must be root)" + elseif msg:find("ENOENT") then + msg = "no such file or directory" + end + print(name .. ": cannot change group of '" .. path .. "': " .. msg) return false end return true diff --git a/Src/Hyperion-bash/bin/chmod b/Src/Hyperion-bash/bin/chmod index 6e97737..269a1e2 100644 --- a/Src/Hyperion-bash/bin/chmod +++ b/Src/Hyperion-bash/bin/chmod @@ -220,7 +220,13 @@ local function chmodPath(path) local ok, cerr = pcall(syscall.chmod, path, newPerms) if not ok then - print(name .. ": cannot change permissions of '" .. path .. "': " .. tostring(cerr)) + local msg = tostring(cerr) + if msg:find("EACCES") or msg:find("EPERM") then + msg = "permission denied" + elseif msg:find("ENOENT") then + msg = "no such file or directory" + end + print(name .. ": cannot change permissions of '" .. path .. "': " .. msg) return false end return true diff --git a/Src/Hyperion-bash/bin/chown b/Src/Hyperion-bash/bin/chown index 4cb32ad..ffabaf4 100644 --- a/Src/Hyperion-bash/bin/chown +++ b/Src/Hyperion-bash/bin/chown @@ -95,14 +95,20 @@ end local function chownPath(path) local stat = syscall.stat(path) if not stat then - print(name .. ": cannot stat '" .. path .. "': No such file or directory") + print(name .. ": cannot stat '" .. path .. "': no such file or directory") return false end local uid = newUid ~= nil and newUid or stat.owner local gid = newGid ~= nil and newGid or stat.group local ok, err = pcall(syscall.chown, path, uid, gid) if not ok then - print(name .. ": cannot change owner of '" .. path .. "': " .. tostring(err)) + local msg = tostring(err) + if msg:find("EPERM") or msg:find("EACCES") then + msg = "operation not permitted (must be root)" + elseif msg:find("ENOENT") then + msg = "no such file or directory" + end + print(name .. ": cannot change owner of '" .. path .. "': " .. msg) return false end return true diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index 5e45fdb..a1a8912 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -17,7 +17,13 @@ local commandHistory = {} local terminate = false syscall.setEnviron("SHELL","rtbash") syscall.setEnviron("PATH","/bin/") -syscall.chdir("/") +local _home = syscall.getEnviron("HOME") +if _home and _home ~= "" then + local ok = pcall(syscall.chdir, _home) + if not ok then syscall.chdir("/") end +else + syscall.chdir("/") +end local oldWD = "" for i = 1, 16 do diff --git a/Src/Hyperion-bash/bin/login b/Src/Hyperion-bash/bin/login index 912e952..ce0756f 100644 --- a/Src/Hyperion-bash/bin/login +++ b/Src/Hyperion-bash/bin/login @@ -96,6 +96,9 @@ local function spawnShell(username, uid, shell, homedir) end local chdirOk, chdirErr = pcall(syscall.chdir, homedir) + if not chdirOk then + pcall(syscall.chdir, "/") + end local ok, err = pcall(syscall.execspawn, shell, username .. ":shell") if not ok then diff --git a/Src/Hyperion-core/sbin/init.lua b/Src/Hyperion-core/sbin/init.lua index b95bce5..574c93a 100644 --- a/Src/Hyperion-core/sbin/init.lua +++ b/Src/Hyperion-core/sbin/init.lua @@ -14,6 +14,9 @@ for i,v in pairs(kernel.processes) do end, i) end +if not fs.exists("/bin/startup") then + fs.mkdir("/bin/startup") +end local files = fs.list("/bin/startup") if not files then error("Failed to list /bin/startup") end for i,v in ipairs(files) do diff --git a/Src/Hyperion-kernel/lib/modules/Hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/Hyperion/10_vfs.kmod index 980f53f..20ed71d 100644 --- a/Src/Hyperion-kernel/lib/modules/Hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/Hyperion/10_vfs.kmod @@ -164,6 +164,9 @@ local function resolveMount(normalPath) return vfs.disks[mountId], diskPath end +-- Expose parser for use by other modules (e.g. permissions seeder) +vfs._parseMetafile = parseMetafile + local function readMetaEntry(disk, parentDiskPath, filename) if filename == ".meta" then error("Cannot open metafile") end local mp @@ -177,6 +180,15 @@ local function readMetaEntry(disk, parentDiskPath, filename) if not ok or not f then return nil end local raw = f.read(65535) if f.close then f.close() end + + -- Auto-upgrade: if this .meta file is not v2, rewrite it as v2 now + if raw and #raw > 0 and raw:byte(1) ~= META_VERSION then + local upgraded = makeMetafile(parseMetafile(raw)) + local wok, wf = pcall(function() return disk:open(mp, "w") end) + if wok and wf then wf.write(upgraded); if wf.close then wf.close() end end + raw = upgraded + end + local parsed = parseMetafile(raw) return parsed[filename] end @@ -418,6 +430,8 @@ function vfs.open(path, mode) if not disk then error("NODISK") end local meta = getFileMeta(path) + local isNew = (mode == "w" or mode == "a") and not disk:fileExists(diskPath) + checkperms(meta, mode == "r" and "r" or "w") local handle @@ -426,6 +440,22 @@ function vfs.open(path, mode) if type(handle) ~= "table" then error("ENFILE") end end + -- If this is a newly created file, stamp it with the creating user's ownership + if isNew then + local euid = (task and (task.euid or task.uid)) or kernel.uid + local egid = (task and task.gid) or 0 + local norm = normalizePath(path) + local parent = norm:match("^(.*)/[^/]+$") or "/" + if parent == "" then parent = "/" end + local name = norm:match("[^/]+$") + if name then + local entry = { etype=0x00, owner=euid, group=egid, + perms=vfs.PERM.RW_R_R, cmeta="" } + pcall(writeMetaEntry, parent, name, entry, false) + meta = entry + end + end + local fobj = newFileObj(handle, mode, path, meta, disk:type(diskPath)) if mode == "r" and bit_is_set(meta.perms, 6) then fobj.suid_owner = meta.owner @@ -592,15 +622,33 @@ function vfs.listdir(path) end function vfs.mkdir(path) + local norm = normalizePath(path) + local parent = norm:match("^(.*)/[^/]+$") or "/" + if parent == "" then parent = "/" end + local parentMeta = getFileMeta(parent) + checkperms(parentMeta, "w") local disk, diskPath = resolvePath(path) - local meta = getFileMeta(path) - checkperms(meta, "w") disk:makeDirectory(diskPath) + -- Stamp the new directory with the creating user's ownership + local task = kernel.currentTask + local euid = (task and (task.euid or task.uid)) or kernel.uid + local egid = (task and task.gid) or 0 + local name = norm:match("[^/]+$") + if name then + local entry = { etype=0x00, owner=euid, group=egid, + perms=vfs.PERM.RWX_RX, cmeta="" } + pcall(writeMetaEntry, parent, name, entry, false) + end end function vfs.remove(path) + local norm = resolveSymlinks(path, true) + local parent = norm:match("^(.*)/[^/]+$") or "/" + if parent == "" then parent = "/" end + local parentMeta = getFileMeta(parent) + checkperms(parentMeta, "w") + local meta = getFileMeta(path, true) - checkperms(meta, "w") if kernel.unixSockets and kernel.unixSockets[path] then kernel.unixSockets[path] = nil diff --git a/Src/Hyperion-kernel/lib/modules/Hyperion/30_userspace.kmod b/Src/Hyperion-kernel/lib/modules/Hyperion/30_userspace.kmod index fa11794..d2fab03 100644 --- a/Src/Hyperion-kernel/lib/modules/Hyperion/30_userspace.kmod +++ b/Src/Hyperion-kernel/lib/modules/Hyperion/30_userspace.kmod @@ -50,10 +50,10 @@ local function readonly(tbl) __metatable = false }) end -local origLoad = load +--local origLoad = load kernel._U = readonly(kernel._G) kernel.allowGlobalOverwrites = true kernel._U._G = kernel._U -kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end +--kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end kernel.allowGlobalOverwrites = false diff --git a/Src/Hyperion-kernel/lib/modules/Hyperion/40_auth.kmod b/Src/Hyperion-kernel/lib/modules/Hyperion/40_auth.kmod index 71c7459..821a64d 100644 --- a/Src/Hyperion-kernel/lib/modules/Hyperion/40_auth.kmod +++ b/Src/Hyperion-kernel/lib/modules/Hyperion/40_auth.kmod @@ -394,6 +394,8 @@ function auth.newUser(username, password, gid, homedir, shell) if kernel.vfs.mkdir and not kernel.vfs.exists(homedir) then kernel.vfs.mkdir(homedir) + -- Homedir must be owned by the new user, not root + pcall(kernel.vfs.chown, homedir, uid, uid) end kernel.log("AUTH: new user '" .. username .. "' uid=" .. tostring(uid)) diff --git a/Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod b/Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod index 0a926d3..da3e8d4 100644 --- a/Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod +++ b/Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod @@ -51,12 +51,106 @@ end local REG = 0x00 -if rootDisk:fileExists(".meta") then - kernel.log("Permissions already seeded, skipping.", "INFO") -else +-- All known /bin entries with their permissions +local BIN_ENTRIES = { + {"cat", REG, 0, 0, RWX_RX_RX}, + {"chattr", REG, 0, 0, RWX_RX_RX}, + {"chgrp", REG, 0, 0, RWX_RX_RX}, + {"chmod", REG, 0, 0, RWX_RX_RX}, + {"chown", REG, 0, 0, RWX_RX_RX}, + {"chroot", REG, 0, 0, RWX_RX_RX}, + {"clear", REG, 0, 0, RWX_RX_RX}, + {"echo", REG, 0, 0, RWX_RX_RX}, + {"hfetch", REG, 0, 0, RWX_RX_RX}, + {"help", REG, 0, 0, RWX_RX_RX}, + {"hysh", REG, 0, 0, RWX_RX_RX}, + {"hyshex", REG, 0, 0, RWX_RX_RX}, + {"id", REG, 0, 0, RWX_RX_RX}, + {"install", REG, 0, 0, RWX_RX_RX}, + {"ln", REG, 0, 0, RWX_RX_RX}, + {"login", REG, 0, 0, SUID_755 }, + {"loimgcreate", REG, 0, 0, RWX_RX_RX}, + {"looptest", REG, 0, 0, RWX_RX_RX}, + {"losetup", REG, 0, 0, RWX_RX_RX}, + {"ls", REG, 0, 0, RWX_RX_RX}, + {"lsusers", REG, 0, 0, RWX_RX_RX}, + {"lua", REG, 0, 0, RWX_RX_RX}, + {"luaold", REG, 0, 0, RWX_RX_RX}, + {"micro", REG, 0, 0, RWX_RX_RX}, + {"mkdir", REG, 0, 0, RWX_RX_RX}, + {"mount", REG, 0, 0, RWX_RX_RX}, + {"passwd", REG, 0, 0, RWX_RX_RX}, + {"ps", REG, 0, 0, RWX_RX_RX}, + {"pwd", REG, 0, 0, RWX_RX_RX}, + {"readlink", REG, 0, 0, RWX_RX_RX}, + {"sed", REG, 0, 0, RWX_RX_RX}, + {"socktest", REG, 0, 0, RWX_RX_RX}, + {"spm", REG, 0, 0, RWX_RX_RX}, + {"su", REG, 0, 0, SUID_755 }, + {"sudo", REG, 0, 0, SUID_755 }, + {"sysdump", REG, 0, 0, RWX_RX_RX}, + {"umount", REG, 0, 0, RWX_RX_RX}, + {"useradd", REG, 0, 0, RWX_RX_RX}, + {"userdel", REG, 0, 0, RWX_RX_RX}, + {"usermod", REG, 0, 0, RWX_RX_RX}, + {"whoami", REG, 0, 0, RWX_RX_RX}, + {"yes", REG, 0, 0, RWX_RX_RX}, + {"startup", REG, 0, 0, RWX_RX_RX}, +} + +-- Merge entries: always ensure all known entries exist with correct permissions. +-- This handles both fresh installs and upgrades (adds missing entries, upgrades +-- the on-disk format to v2 by rewriting). +local function mergeMeta(dir, entries) + local diskDir = dir + if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end + local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta") + + -- Read existing meta (may be v1 or v2) + local existing = {} + local rok, rf = pcall(function() return rootDisk:open(metaPath, "r") end) + if rok and rf then + local raw = rf.read(65535) + if rf.close then rf.close() end + -- Parse using the VFS parser (handles v0/v1/v2) + existing = kernel.vfs and kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw) or {} + end + + -- Add any missing entries (don't overwrite existing customised perms) + for _, e in ipairs(entries) do + if not existing[e[1]] then + existing[e[1]] = { + etype = e[2] or 0x00, + owner = e[3] or 0, + group = e[4] or 0, + perms = e[5] or RWX_RX_RX, + cmeta = e[6] or "", + } + end + end + + -- Write back as v2 + local data = string.char(META_VERSION) + for name, m in pairs(existing) do + data = data .. makeEntry(name, m.etype or 0x00, m.owner or 0, m.group or 0, m.perms or RWX_RX_RX, m.cmeta or "") + end + + local ok, err = pcall(function() + local f = rootDisk:open(metaPath, "w") + f.write(data) + f.close() + end) + if not ok then + kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8) + end +end + +local freshInstall = not rootDisk:fileExists(".meta") + +if freshInstall then kernel.log("Seeding filesystem permissions...", "INFO") - -- / + -- / (only on fresh install — these dirs are stable) writeMeta("/", { {"bin", REG, 0, 0, RWX_RX_RX}, {"boot", REG, 0, 0, RWX_RX_RX}, @@ -71,38 +165,10 @@ else {"var", REG, 0, 0, RWX_RX_RX}, }) - -- /bin - writeMeta("/bin", { - {"cat", REG, 0, 0, RWX_RX_RX}, - {"clear", REG, 0, 0, RWX_RX_RX}, - {"echo", REG, 0, 0, RWX_RX_RX}, - {"hfetch", REG, 0, 0, RWX_RX_RX}, - {"hysh", REG, 0, 0, RWX_RX_RX}, - {"hyshex", REG, 0, 0, RWX_RX_RX}, - {"install", REG, 0, 0, RWX_RX_RX}, - {"login", REG, 0, 0, SUID_755 }, - {"ls", REG, 0, 0, RWX_RX_RX}, - {"lua", REG, 0, 0, RWX_RX_RX}, - {"luaold", REG, 0, 0, RWX_RX_RX}, - {"mkdir", REG, 0, 0, RWX_RX_RX}, - {"ps", REG, 0, 0, RWX_RX_RX}, - {"pwd", REG, 0, 0, RWX_RX_RX}, - {"spm", REG, 0, 0, RWX_RX_RX}, - {"su", REG, 0, 0, SUID_755 }, - {"sudo", REG, 0, 0, SUID_755 }, - {"sysdump", REG, 0, 0, RWX_RX_RX}, - {"whoami", REG, 0, 0, RWX_RX_RX}, - {"yes", REG, 0, 0, RWX_RX_RX}, - {"startup", REG, 0, 0, RWX_RX_RX}, - {"ln", REG, 0, 0, RWX_RX_RX}, - {"readlink", REG, 0, 0, RWX_RX_RX}, - }) - writeMeta("/bin/startup", { {"test.lua", REG, 0, 0, RWX_RX_RX}, }) - -- /etc writeMeta("/etc", { {"passwd", REG, 0, 0, RW_R_R}, {"shadow", REG, 0, 0, RW____}, @@ -113,12 +179,10 @@ else {"secret", REG, 0, 0, RW____}, }) - -- /sbin writeMeta("/sbin", { {"init.lua", REG, 0, 0, RWX_RX_RX}, }) - -- /boot writeMeta("/boot", { {"kernel.lua", REG, 0, 0, RW_R_R }, {"boot.cfg", REG, 0, 0, RW_R_R }, @@ -129,7 +193,6 @@ else {"oc", REG, 0, 0, RWX_RX_RX}, }) - -- /lib writeMeta("/lib", { {"sys", REG, 0, 0, RWX_RX_RX}, {"modules", REG, 0, 0, RWX_RX_RX}, @@ -141,6 +204,11 @@ else }) kernel.log("Filesystem permissions seeded.", "INFO") +else + kernel.log("Permissions already seeded, merging /bin updates...", "INFO") end +-- Always merge /bin — adds missing entries and upgrades format to v2 +mergeMeta("/bin", BIN_ENTRIES) + kernel.log("Permission module loaded.", "INFO") diff --git a/Src/Hyperion-kernel/lib/modules/Hyperion/99_final.kmod b/Src/Hyperion-kernel/lib/modules/Hyperion/99_final.kmod new file mode 100644 index 0000000..230a54f --- /dev/null +++ b/Src/Hyperion-kernel/lib/modules/Hyperion/99_final.kmod @@ -0,0 +1,5 @@ +local args = {...} +local kernel = args[1] + +local origLoad = load +--kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end \ No newline at end of file