Got rid of the nasty security-request system.

The new system is more hook-like, which is both good & bad,
 but frankly mostly good given the complexity out of kernel.
This commit is contained in:
20kdc 2018-03-19 03:08:09 +00:00
parent 4a57c9f175
commit 0e97fbbfd6
7 changed files with 137 additions and 129 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: <any>
-- 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.<hw>": Component
-- "s.<event>": 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.<endpoint>": This access is actually useless on it's own - it is given by x.<endpoint>
-- "k.<x>": 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.<endpoint>": 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

View File

@ -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

View File

@ -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),