diff --git a/code/apps/sys-everest.lua b/code/apps/sys-everest.lua index 1e0766e..28ecd1f 100644 --- a/code/apps/sys-everest.lua +++ b/code/apps/sys-everest.lua @@ -59,9 +59,6 @@ monitors[0] = {nil, nil, 80, 25} -- line y local surfaces = {} --- Dead process's switch to clean up resources by crashing processes which "just don't get it" -local dead = false - local savingThrow = neo.requestAccess("x.neo.sys.manage") if savingThrow then savingThrow.registerForShutdownEvent() @@ -73,7 +70,7 @@ if savingThrow then -- In this case the surfaces are leaked and hold references here. They have to be removed manually. -- Do this via a "primary event" (k.deregistration) and "deathtrap events" -- If a process evades the deathtrap then it clearly has reason to stay alive regardless of Everest status. - dead = true + -- Also note, should savingThrow fail, neo.dead is now a thing. monitors = {} for _, v in ipairs(surfaces) do pcall(v[6], "line", 1) @@ -335,7 +332,7 @@ everestProvider(function (pkg, pid, sendSig) local base = pkg .. "/" .. pid local lid = 0 return function (w, h, title) - if dead then error("everest died") end + if neo.dead then error("everest died") end w = math.floor(math.max(w, 8)) h = math.floor(math.max(h, 1)) + 1 if type(title) ~= "string" then @@ -410,7 +407,7 @@ everestProvider(function (pkg, pid, sendSig) return { id = llid, setSize = function (w, h) - if dead then return end + if neo.dead then return end w = math.floor(math.max(w, 8)) h = math.floor(math.max(h, 1)) + 1 local _, x, y = ensureOnscreen(surf[1], surf[2], surf[3], w, h) @@ -418,7 +415,7 @@ everestProvider(function (pkg, pid, sendSig) return w, (h - 1) end, span = function (x, y, text, bg, fg) - if dead then error("everest died") end + if neo.dead then error("everest died") end if type(x) ~= "number" then error("X must be number.") end if type(y) ~= "number" then error("Y must be number.") end if type(bg) ~= "number" then error("Background must be number.") end @@ -429,7 +426,7 @@ everestProvider(function (pkg, pid, sendSig) handleSpan(surf, x, y + 1, text, bg, fg) end, close = function () - if dead then return end + if neo.dead then return end local os1 = surfaces[1] surfaceOwners[surf] = nil for k, v in ipairs(surfaces) do diff --git a/code/apps/sys-glacier.lua b/code/apps/sys-glacier.lua index 6b33612..9892d62 100644 --- a/code/apps/sys-glacier.lua +++ b/code/apps/sys-glacier.lua @@ -4,17 +4,15 @@ -- s-donkonit : config, shutdown, screens -- Doesn't matter what calls this service, because there's a mutex here. -local donkonitSPProvider = neo.requestAccess("r.neo.sys.manage") -- Restrict to s- -if not donkonitSPProvider then return end -local donkonitRDProvider = neo.requestAccess("r.neo.sys.screens") -if not donkonitRDProvider then return end +local donkonitSPProvider = neo.requireAccess("r.neo.sys.manage", "creating NEO core APIs") -- Restrict to s- +local donkonitRDProvider = neo.requireAccess("r.neo.sys.screens", "creating NEO core APIs") -local computer = neo.requestAccess("k.computer") -local fs = neo.requestAccess("c.filesystem") -local gpus = neo.requestAccess("c.gpu") -local screens = neo.requestAccess("c.screen") -neo.requestAccess("s.h.component_added") -neo.requestAccess("s.h.component_removed") +local computer = neo.requireAccess("k.computer", "shutting down") +local fs = neo.requireAccess("c.filesystem", "settings I/O") +local gpus = neo.requireAccess("c.gpu", "screen control") +local screens = neo.requireAccess("c.screen", "screen control") +neo.requireAccess("s.h.component_added", "HW management") +neo.requireAccess("s.h.component_removed", "HW management") local function shutdownFin(reboot) -- any final actions donkonit needs to take here diff --git a/code/apps/sys-icecap.lua b/code/apps/sys-icecap.lua index 91eda3f..eae1bcc 100644 --- a/code/apps/sys-icecap.lua +++ b/code/apps/sys-icecap.lua @@ -9,15 +9,11 @@ local neoux, err = require("neoux") if not neoux then error(err) end -- This app is basically neoux's testcase neoux = neoux(event, neo) -local donkonit = neo.requestAccess("x.neo.sys.manage") -if not donkonit then error("needs donkonit for sysconf access") end -local scr = neo.requestAccess("s.k.securityrequest") -if not scr then error("no security request access") end +local settings = neo.requireAccess("x.neo.sys.manage", "security sysconf access") -local fs = neo.requestAccess("c.filesystem") +local fs = neo.requireAccess("c.filesystem", "file managers") -local donkonitDFProvider = neo.requestAccess("r.neo.pub.base") -if not donkonitDFProvider then return end +local donkonitDFProvider = neo.requireAccess("r.neo.pub.base", "creating basic NEO APIs") local targsDH = {} -- data disposal @@ -110,14 +106,34 @@ donkonitDFProvider(function (pkg, pid, sendSig) } end) -event.listen("k.securityrequest", function (evt, pkg, pid, perm, rsp) +-- Connect in security policy now +local rootAccess = neo.requireAccess("k.root", "installing GUI integration") +local backup = rootAccess.securityPolicyINIT or rootAccess.securityPolicy +rootAccess.securityPolicyINIT = backup +rootAccess.securityPolicy = function (pid, proc, req) + if neo.dead then + return backup(pid, proc, req) + end + req.result = proc.pkg:sub(1, 4) == "sys-" local secpol, err = require("sys-secpolicy") if not secpol then - rsp(false) - error("Bad security policy: " .. err) + -- Failsafe. + neo.emergency("Used fallback policy because of load-err: " .. err) + req.service() + return end - secpol(neoux, donkonit, pkg, pid, perm, rsp) -end) + -- Push to ICECAP thread to avoid deadlock on neoux b/c wrong event-pull context + event.runAt(0, function () + local ok, err = pcall(secpol, neoux, settings, proc.pkg, pid, req.perm, function (r) + req.result = r + req.service() + end) + if not ok then + neo.emergency("Used fallback policy because of run-err: " .. err) + req.service() + end + end) +end event.listen("k.procdie", function (evt, pkg, pid, reason) if targsDH[pid] then diff --git a/code/apps/sys-init.lua b/code/apps/sys-init.lua index 6d07ed5..5dce2f7 100644 --- a/code/apps/sys-init.lua +++ b/code/apps/sys-init.lua @@ -281,6 +281,31 @@ end local function initializeSystem() -- System has just booted, bristol is in charge + -- IMMEDIATELY install security policy in a TSR-like manner, + -- using root privs to keep it going when sys-init's not around + local rootAccess = neo.requireAccess("k.root", "installing security (YOU SHOULD NEVER SEE THIS)") + rootAccess.securityPolicy = function (pid, proc, req) + req.result = proc.pkg:sub(1, 4) == "sys-" + local secpol, err = require("sys-secpolicy") + if not secpol then + -- Failsafe. + neo.emergency("Used fallback policy because of load-err: " .. err) + req.service() + end + local settings + pcall(function () + -- basically pull system settings from thin air + settings = rootAccess.retrieveAccess("x.neo.sys.manage", "sys-init", -1) + end) + local ok, err = pcall(secpol, nil, settings, proc.pkg, pid, req.perm, function (r) + req.result = r + req.service() + end) + if not ok then + neo.emergency("Used fallback policy because of run-err: " .. err) + req.service() + end + end -- Firstly, initialize hardware to something sensible since we don't know scrcfg gpu = neo.requestAccess("c.gpu").list()() if gpu then diff --git a/code/init.lua b/code/init.lua index a675ce7..51953e1 100644 --- a/code/init.lua +++ b/code/init.lua @@ -16,7 +16,7 @@ local readBufSize = 2048 -- A function used for logging, usable by programs. -- Comment this out if you don't want programs to have -- access to ocemu's logger. -local emergencyFunction +local emergencyFunction = function () end local ocemu = component.list("ocemu", true)() if ocemu then ocemu = component.proxy(ocemu) @@ -25,6 +25,7 @@ end primaryDisk = component.proxy(computer.getBootAddress()) +-- {{time, func, arg1...}...} timers = {} libraries = {} @@ -36,7 +37,7 @@ setmetatable(libraries, { -- proc.pkg = "pkg" -- proc.access = {["perm"] = true, ...} -- proc.denied = {["perm"] = true, ...} --- proc.deathcbs = {function(), ...} +-- proc.deathCBs = {function(), ...} -- very slightly adjusted total CPU time -- proc.cpuUsage processes = {} @@ -44,12 +45,6 @@ processes = {} accesses = {} local lastPID = 0 --- keys: --- sr.waiting: keys are PIDs, values are just "true" --- sr.service: function() --- sr.result: boolean -local outstandingSR = {} - -- Kernel global "idle time" counter, useful for accurate performance data local idleTime = 0 @@ -184,7 +179,7 @@ local function termProc(pid, reason) -- Immediately prepare for GC, it's possible this is out of memory. -- If out of memory, then to reduce risk of memory leak by error, memory needs to be freed ASAP. -- Start by getting rid of all process data. - local dcbs = processes[pid].deathcbs + local dcbs = processes[pid].deathCBs local pkg = processes[pid].pkg local usage = processes[pid].cpuUsage processes[pid] = nil @@ -197,7 +192,7 @@ local function termProc(pid, reason) if reason and criticalFailure then error(tostring(reason)) -- This is a debugging aid to give development work an easy-to-get-at outlet. Icecap is for most cases end - if reason and emergencyFunction then + if reason then emergencyFunction("d1 " .. pkg .. "/" .. pid) emergencyFunction("d2 " .. reason) end @@ -240,18 +235,16 @@ function distEvent(pid, s, ...) if not v then return end - if (not v.access["s." .. s]) or v.access["k.root"] then + if not (v.access["s." .. s] or v.access["k.root"]) then return end - -- Schedule a timer for "now" - table.insert(timers, {computer.uptime(), function () - return execEvent(pid, s, table.unpack(ev)) - end}) - return + -- Schedule the timer to carry the event. + table.insert(timers, {0, execEvent, pid, s, table.unpack(ev)}) + else + for k, v in pairs(processes) do + distEvent(k, s, ...) + end end - for k, v in pairs(processes) do - distEvent(k, s, ...) - end end local loadLibraryInner = nil @@ -337,6 +330,26 @@ function loadLibraryInner(library) return nil, r end +-- These two are hooks for k.root level applications to change policy. +-- Only a k.root application is allowed to do this for obvious reasons. +function securityPolicy(pid, proc, req) + -- Important safety measure : only sys-init gets anything until sys-init decides what to do. + req.result = proc.pkg == "sys-init" + req.service() +end +function runProgramPolicy(ipkg, pkg, pid, ...) + -- VERY specific injunction here: + -- non "sys-" apps NEVER start "sys-" apps + -- This is part of the "default security policy" below: + -- sys- has all access + -- anything else has none + if ipkg:sub(1, 4) == "sys-" then + if pkg:sub(1, 4) ~= "sys-" then + return nil, "non-sys app trying to start sys app" + end + end +end + function retrieveAccess(perm, pkg, pid) -- Return the access lib and the death callback. @@ -344,12 +357,16 @@ function retrieveAccess(perm, pkg, pid) -- "c.": Component -- "s.": Signal receiver (with responsibilities for Security Request watchers) -- "s.k.<...>": Kernel stuff - -- "s.k.securityrequest": !!! HAS SIDE EFFECTS !!! + -- "s.k.procnew" : New process (pkg, pid) + -- "s.k.procdie" : Process dead (pkg, pid, reason, usage) + -- "s.k.registration" : Registration of service alert ("x." .. etc) + -- "s.k.deregistration" : Registration of service alert ("x." .. etc) + -- "s.k.securityresponse" : Response from security policy (accessId, accessObj) -- "s.h.<...>": Incoming HW messages -- "s.x.": This access is actually useless on it's own - it is given by x. -- "k.": Kernel - -- "k.root": _ENV (holy grail) + -- "k.root": _ENV (holy grail), and indirectly security request control (which is basically equivalent to this) -- "k.computer": computer -- "r.": Registration Of Service... @@ -441,24 +458,15 @@ end local start = nil function start(pkg, ...) - ensureType(pkg, "string") - ensurePathComponent(pkg .. ".lua") local args = {...} local proc = {} local pid = lastPID lastPID = lastPID + 1 local function startFromUser(ipkg, ...) - -- VERY specific injunction here: - -- non "sys-" apps NEVER start "sys-" apps - -- This is part of the "default security policy" below: - -- sys- has all access - -- anything else has none - if ipkg:sub(1, 4) == "sys-" then - if pkg:sub(1, 4) ~= "sys-" then - return nil, "non-sys app trying to start sys app" - end - end + ensureType(pkg, "string") + ensurePathComponent(pkg .. ".lua") + runProgramPolicy(ipkg, pkg, pid, ...) return start(ipkg, pkg, pid, ...) end @@ -479,16 +487,17 @@ function start(pkg, ...) ensureType(perm, "string") -- Safety-checked, prepare security event. local req = {} - req.waiting = {} + req.perm = perm req.service = function () if processes[pid] then local n = nil local n2 = nil if req.result then proc.access[perm] = true + proc.denied[perm] = nil n, n2 = retrieveAccess(perm, pkg, pid) if n2 then - table.insert(processes[pid].deathcbs, n2) + table.insert(processes[pid].deathCBs, n2) end else proc.denied[perm] = true @@ -496,40 +505,21 @@ function start(pkg, ...) distEvent(pid, "k.securityresponse", perm, n) end end - req.result = (not proc.denied[perm]) or proc.access["k.root"] + -- outer security policy: + req.result = proc.access["k.root"] or not proc.denied[perm] + if proc.access["k.root"] or proc.access[perm] or proc.denied[perm] then -- Use cached result to prevent possible unintentional security service spam req.service() return end - -- Anything with s.k.securityrequest access has the response function and thus the vote, - -- but can't vote on itself for obvious reasons. Kernel judge is a fallback. - local shouldKernelJudge = true - for k, v in pairs(processes) do - if v.access["s.k.securityrequest"] then - shouldKernelJudge = false - if k ~= pid then - req.waiting[k] = true - distEvent(k, "k.securityrequest", pkg, pid, perm, function (r) - ensureType(r, "boolean") - if not r then - req.result = false - end - req.waiting[k] = nil - end) - end - end - end - if shouldKernelJudge then - -- Rather restrictive, but an important safety measure - req.result = pkg:sub(1, 4) == "sys-" - req.service() - else - table.insert(outstandingSR, req) - end + -- Denied goes to on to prevent spam + proc.denied[perm] = true + securityPolicy(pid, proc, req) end local env = baseProcEnv() env.neo.pid = pid + env.neo.dead = false env.neo.executeAsync = startFromUser env.neo.execute = function (...) return osExecuteCore(function () end, ...) @@ -548,12 +538,16 @@ function start(pkg, ...) handler(table.unpack(n)) end end + env.neo.requireAccess = function (perm, reason) + -- Allows for hooking + local res = env.neo.requestAccess(perm) + if not res then error(pkg .. " needed " .. perm .. " for " .. (reason or "some reason")) end + return res + end env.neo.scheduleTimer = function (time) ensureType(time, "number") local tag = {} - table.insert(timers, {time, function(ofs) - return execEvent(pid, "k.timer", tag, time, ofs) - end}) + table.insert(timers, {time, execEvent, pid, "k.timer", tag, time, ofs}) return tag end @@ -571,11 +565,13 @@ function start(pkg, ...) ["s.k.procdie"] = true, -- Used when a registration is updated, in particular, as this signifies "readiness" ["s.k.registration"] = true, + ["s.k.deregistration"] = true } proc.denied = {} - proc.deathcbs = {} + -- You are dead. Not big surprise. + proc.deathCBs = {function () pcall(function () env.neo.dead = true end) end} proc.cpuUsage = 0 - -- Note the target process doesn't get the procnew (it's executed after it's creation) + -- Note the target process doesn't get the procnew (the dist occurs before it's creation) pcall(distEvent, nil, "k.procnew", pkg, pid) processes[pid] = proc -- For processes waiting on others, this at least tries to guarantee some safety. @@ -589,34 +585,10 @@ function start(pkg, ...) return pid end --- Main Scheduling Loop -- - -local function processSRs() - local didAnything = false - for k, v in pairs(outstandingSR) do - -- Outstanding security request handler. - local actualWaitingCount = 0 - for k2, _ in pairs(v.waiting) do - if not processes[k2] then - v.waiting[k2] = nil - v.result = false - else - actualWaitingCount = actualWaitingCount + 1 - end - end - if actualWaitingCount == 0 then - -- Service the SR - outstandingSR[k].service() - outstandingSR[k] = nil - didAnything = true - end - end - return didAnything -end - --- The actual loop & initialization +-- Kernel Scheduling Loop -- if not start("sys-init") then error("Could not start sys-init") end + while true do local tmr = nil for i = 1, 16 do @@ -628,7 +600,7 @@ while true do while timers[k] do local v = timers[k] if v[1] <= now then - if v[2](now - v[1]) then + if v[2](table.unpack(v, 3)) then breaking = true tmr = 0.05 break @@ -649,7 +621,6 @@ while true do end end if breaking then break end - didAnything = didAnything or processSRs() -- If the system didn't make any progress, then we're waiting for a signal (this includes timers) if not didAnything then break end end diff --git a/code/libs/neoux.lua b/code/libs/neoux.lua index 71a61ef..129af90 100644 --- a/code/libs/neoux.lua +++ b/code/libs/neoux.lua @@ -22,7 +22,7 @@ return function (event, neo) lclEvToW = {} end local function pushWindowToEverest(k) - local everest = retrieveEverest() + local everest = retrieveEverest() if not everest then everestDied() return diff --git a/code/libs/sys-secpolicy.lua b/code/libs/sys-secpolicy.lua index 2c7482c..45f9254 100644 --- a/code/libs/sys-secpolicy.lua +++ b/code/libs/sys-secpolicy.lua @@ -53,12 +53,11 @@ end return function (neoux, settings, pkg, pid, perm, rsp) local res = actualPolicy(pkg, pid, perm) - if res == "ask" then + if res == "ask" and settings then res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or "ask" end - if res == "ask" then + if res == "ask" and neoux then local fmt = neoux.fmtText(unicode.safeTextFormat(string.format("%s/%i wants:\n%s\nAllow this?", pkg, pid, perm)), 20) - local always = "Always" local yes = "Yes" local no = "No" @@ -73,7 +72,9 @@ return function (neoux, settings, pkg, pid, perm, rsp) w.close() end), neoux.tcbutton((#yes) + 3, #fmt + 2, always, function (w) - settings.setSetting("perm|" .. pkg .. "|" .. perm, "allow") + if settings then + settings.setSetting("perm|" .. pkg .. "|" .. perm, "allow") + end rsp(true) w.close() end),