summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/util-sbx.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/util-sbx.lua')
-rw-r--r--tex/context/base/mkiv/util-sbx.lua529
1 files changed, 388 insertions, 141 deletions
diff --git a/tex/context/base/mkiv/util-sbx.lua b/tex/context/base/mkiv/util-sbx.lua
index 260e8b3b5..66a650875 100644
--- a/tex/context/base/mkiv/util-sbx.lua
+++ b/tex/context/base/mkiv/util-sbx.lua
@@ -23,19 +23,23 @@ local platform = os.type
local P, S, C = lpeg.P, lpeg.S, lpeg.C
local gsub = string.gsub
local lower = string.lower
+local find = string.find
+local concat = string.concat
local unquoted = string.unquoted
local optionalquoted = string.optionalquoted
+local basename = file.basename
local sandbox = sandbox
local validroots = { }
local validrunners = { }
-local validbinaries = { }
+local validbinaries = true -- all permitted
+local validlibraries = true -- all permitted
local validators = { }
-local p_validroot = nil
local finalized = nil
-local norunners = false
local trace = false
-local p_split = lpeg.tsplitat(" ") -- more spaces?
+
+local p_validroot = nil
+local p_split = lpeg.firstofsplit(" ")
local report = logs.reporter("sandbox")
@@ -43,69 +47,87 @@ trackers.register("sandbox",function(v) trace = v end) -- often too late anyway
sandbox.setreporter(report)
-sandbox.finalizer(function()
- finalized = true
-end)
+sandbox.finalizer {
+ category = "files",
+ action = function()
+ finalized = true
+ end
+}
local function registerroot(root,what) -- what == read|write
if finalized then
report("roots are already finalized")
else
- root = collapsepath(expandname(root))
- if platform == "windows" then
- root = lower(root) -- we assume ascii names
+ if type(root) == "table" then
+ root, what = root[1], root[2]
+ end
+ if type(root) == "string" and root ~= "" then
+ root = collapsepath(expandname(root))
+ -- if platform == "windows" then
+ -- root = lower(root) -- we assume ascii names
+ -- end
+ if what == "r" or what == "ro" or what == "readable" then
+ what = "read"
+ elseif what == "w" or what == "wo" or what == "writable" then
+ what = "write"
+ end
+ -- true: read & write | false: read
+ validroots[root] = what == "write" or false
end
- -- true: read & write | false: read
- validroots[root] = what == "write" or false
end
end
-sandbox.finalizer(function() -- initializers can set the path
- if p_validroot then
- report("roots are already initialized")
- else
- sandbox.registerroot(".","write") -- always ok
- -- also register texmf as read
- for name in sortedhash(validroots) do
- if p_validroot then
- p_validroot = P(name) + p_validroot
- else
- p_validroot = P(name)
+sandbox.finalizer {
+ category = "files",
+ action = function() -- initializers can set the path
+ if p_validroot then
+ report("roots are already initialized")
+ else
+ sandbox.registerroot(".","write") -- always ok
+ -- also register texmf as read
+ for name in sortedhash(validroots) do
+ if p_validroot then
+ p_validroot = P(name) + p_validroot
+ else
+ p_validroot = P(name)
+ end
end
+ p_validroot = p_validroot / validroots
end
- p_validroot = p_validroot / validroots
end
-end)
+}
-local function registerrunner(specification)
+local function registerbinary(name)
if finalized then
- report("runners are already finalized")
- else
- local name = specification.name
- if not name then
- report("no runner name specified")
+ report("binaries are already finalized")
+ elseif type(name) == "string" and name ~= "" then
+ if not validbinaries then
return
end
- local program = specification.program
- if type(program) == "string" then
- -- common for all platforms
- elseif type(program) == "table" then
- program = program[platform]
- end
- if type(program) ~= "string" or program == "" then
- report("invalid runner %a specified for platform %a",name,platform)
- return
+ if validbinaries == true then
+ validbinaries = { [name] = true }
+ else
+ validbinaries[name] = true
end
- specification.program = program
- validrunners[name] = specification
+ elseif name == true then
+ validbinaries = { }
end
end
-local function registerbinary(name)
+local function registerlibrary(name)
if finalized then
- report("binaries are already finalized")
+ report("libraries are already finalized")
elseif type(name) == "string" and name ~= "" then
- validbinaries[name] = true
+ if not validlibraries then
+ return
+ end
+ if validlibraries == true then
+ validlibraries = { [name] = true }
+ else
+ validlibraries[name] = true
+ end
+ elseif name == true then
+ validlibraries = { }
end
end
@@ -134,9 +156,9 @@ end
local function validfilename(name,what)
if p_validroot and type(name) == "string" and lpegmatch(p_path,name) then
local asked = collapsepath(expandname(name))
- if platform == "windows" then
- asked = lower(asked) -- we assume ascii names
- end
+ -- if platform == "windows" then
+ -- asked = lower(asked) -- we assume ascii names
+ -- end
local okay = lpegmatch(p_validroot,asked)
if okay == true then
-- read and write access
@@ -163,39 +185,53 @@ local function validfilename(name,what)
end
return name
end
- else
- if filenamelogger then
- filenamelogger(name,"*",name,false)
- end
+ elseif filenamelogger then
+ filenamelogger(name,"*",name,false)
end
else
return name
end
end
-local function readable(name)
- if platform == "windows" then
- name = lower(name) -- we assume ascii names
- end
+local function readable(name,finalized)
+-- if platform == "windows" then -- yes or no
+-- name = lower(name) -- we assume ascii names
+-- end
+ return validfilename(name,"r")
+end
+
+local function normalizedreadable(name,finalized)
+-- if platform == "windows" then -- yes or no
+-- name = lower(name) -- we assume ascii names
+-- end
local valid = validfilename(name,"r")
if valid then
return normalized(valid)
end
end
-local function writeable(name)
- if platform == "windows" then
- name = lower(name) -- we assume ascii names
- end
+local function writeable(name,finalized)
+-- if platform == "windows" then
+-- name = lower(name) -- we assume ascii names
+-- end
+ return validfilename(name,"w")
+end
+
+local function normalizedwriteable(name,finalized)
+-- if platform == "windows" then
+-- name = lower(name) -- we assume ascii names
+-- end
local valid = validfilename(name,"w")
if valid then
return normalized(valid)
end
end
-validators.readable = readable
-validators.writeable = writeable
-validators.filename = readable
+validators.readable = readable
+validators.writeable = normalizedwriteable
+validators.normalizedreadable = normalizedreadable
+validators.normalizedwriteable = writeable
+validators.filename = readable
table.setmetatableindex(validators,function(t,k)
if k then
@@ -204,23 +240,39 @@ table.setmetatableindex(validators,function(t,k)
return readable
end)
-function validators.string(s)
- return s -- can be used to prevent filename checking
+function validators.string(s,finalized)
+ -- can be used to prevent filename checking (todo: only when registered)
+ if finalized and suspicious(s) then
+ return ""
+ else
+ return s
+ end
end
--- end of validators
+function validators.cache(s)
+ if finalized then
+ return basename(s)
+ else
+ return s
+ end
+end
+
+function validators.url(s)
+ if finalized and find("^file:") then
+ return ""
+ else
+ return s
+ end
+end
-sandbox.registerroot = registerroot
-sandbox.registerrunner = registerrunner
-sandbox.registerbinary = registerbinary
-sandbox.validfilename = validfilename
+-- end of validators
local function filehandlerone(action,one,...)
local checkedone = validfilename(one)
if checkedone then
return action(one,...)
else
--- report("file %a is unreachable",one)
+ -- report("file %a is unreachable",one)
end
end
@@ -231,10 +283,10 @@ local function filehandlertwo(action,one,two,...)
if checkedtwo then
return action(one,two,...)
else
--- report("file %a is unreachable",two)
+ -- report("file %a is unreachable",two)
end
else
--- report("file %a is unreachable",one)
+ -- report("file %a is unreachable",one)
end
end
@@ -254,101 +306,289 @@ end
-- runners can be strings or tables
--
-- os.execute : string
--- os.exec : table with program in [0|1]
--- os.spawn : table with program in [0|1]
+-- os.exec : string or table with program in [0|1]
+-- os.spawn : string or table with program in [0|1]
--
-- our execute: registered program with specification
-local function runhandler(action,name,specification)
- local kind = type(name)
- if kind ~= "string" then
+local osexecute = sandbox.original(os.execute)
+local iopopen = sandbox.original(io.popen)
+local reported = { }
+
+local function validcommand(name,program,template,checkers,defaults,variables,reporter,strict)
+ if validbinaries ~= false and (validbinaries == true or validbinaries[program]) then
+ if variables then
+ for variable, value in next, variables do
+ local checker = validators[checkers[variable]]
+ if checker then
+ value = checker(unquoted(value),strict)
+ if value then
+ variables[variable] = optionalquoted(value)
+ else
+ report("variable %a with value %a fails the check",variable,value)
+ return
+ end
+ else
+ report("variable %a has no checker",variable)
+ return
+ end
+ end
+ for variable, default in next, defaults do
+ local value = variables[variable]
+ if not value or value == "" then
+ local checker = validators[checkers[variable]]
+ if checker then
+ default = checker(unquoted(default),strict)
+ if default then
+ variables[variable] = optionalquoted(default)
+ else
+ report("variable %a with default %a fails the check",variable,default)
+ return
+ end
+ end
+ end
+ end
+ end
+ local command = program .. " " .. replace(template,variables)
+ if reporter then
+ reporter("executing runner %a: %s",name,command)
+ elseif trace then
+ report("executing runner %a: %s",name,command)
+ end
+ return command
+ elseif not reported[name] then
+ report("executing program %a of runner %a is not permitted",program,name)
+ reported[name] = true
+ end
+end
+
+local runners = {
+ --
+ -- name,program,template,checkers,variables,reporter
+ --
+ resultof = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("resultof: %s",command)
+ end
+ local handle = iopopen(command,"r") -- already has flush
+ if handle then
+ local result = handle:read("*all") or ""
+ handle:close()
+ return result
+ end
+ end
+ end,
+ execute = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("execute: %s",command)
+ end
+ return osexecute(command)
+ end
+ end,
+ pipeto = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("pipeto: %s",command)
+ end
+ return iopopen(command,"w") -- already has flush
+ end
+ end,
+}
+
+function sandbox.registerrunner(specification)
+ if type(specification) == "string" then
+ local wrapped = validrunners[specification]
+ inspect(table.sortedkeys(validrunners))
+ if wrapped then
+ return wrapped
+ else
+ report("unknown predefined runner %a",specification)
+ return
+ end
+ end
+ if type(specification) ~= "table" then
+ report("specification should be a table (or string)")
return
end
- if norunners then
- report("no runners permitted, ignoring command: %s",name)
+ local name = specification.name
+ if type(name) ~= "string" then
+ report("invalid name, string expected",name)
return
end
- local spec = validrunners[name]
- if not spec then
- report("unknown runner: %s",name)
+ if validrunners[name] then
+ report("invalid name, runner %a already defined")
return
end
- -- specs are already checked
- local program = spec.program
- local variables = { }
- local checkers = spec.checkers or { }
- if specification then
- -- we only handle runners that are defined before the sandbox is
- -- closed so in principle we cannot have user runs with no files
- -- while for context runners we assume a robust specification
- for k, v in next, specification do
- local checker = validators[checkers[k]]
- local value = checker(unquoted(v)) -- todo: write checkers
- if value then
- variables[k] = optionalquoted(value)
- else
- report("suspicious argument found, run blocked: %s",v)
- return
- end
- end
+ local program = specification.program
+ if type(program) == "string" then
+ -- common for all platforms
+ elseif type(program) == "table" then
+ program = program[platform] or program.default or program.unix
+ end
+ if type(program) ~= "string" or program == "" then
+ report("invalid runner %a specified for platform %a",name,platform)
+ return
end
- local command = replace(program,variables)
- if trace then
- report("executing runner: %s",command)
+ local template = specification.template
+ if not template then
+ report("missing template for runner %a",name)
+ return
end
- return action(command)
+ local method = specification.method or "execute"
+ local checkers = specification.checkers or { }
+ local defaults = specification.defaults or { }
+ local runner = runners[method]
+ if runner then
+ local finalized = finalized -- so, the current situation is frozen
+ local wrapped = function(variables)
+ return runner(name,program,template,checkers,defaults,variables,specification.reporter,finalized)
+ end
+ validrunners[name] = wrapped
+ return wrapped
+ else
+ validrunners[name] = nil
+ report("invalid method for runner %a",name)
+ end
+end
+
+function sandbox.getrunner(name)
+ return name and validrunners[name]
end
--- only registered (from list) -- no checking on writable so let's assume harmless
--- runs
+local function suspicious(str)
+ return (find(str,"[/\\]") or find(command,"%.%.")) and true or false
+end
-local function binaryhandler(action,name)
- local kind = type(name)
- local list = name
- if kind == "string" then
- list = lpegmatch(p_split,name)
+local function binaryrunner(action,command,...)
+ if validbinaries == false then
+ -- nothing permitted
+ report("no binaries permitted, ignoring command: %s",command)
+ return
end
- local program = name[0] or name[1]
- if type(program) ~= "string" or program == "" then
- return --silently ignore
+ if type(command) ~= "string" then
+ -- we only handle strings, maybe some day tables
+ report("command should be a string")
+ return
end
- if norunners then
- report("no binaries permitted, ignoring command: %s",program)
+ local program = lpegmatch(p_split,command)
+ if not program or program == "" then
+ report("unable to filter binary from command: %s",command)
return
end
- if not validbinaries[program] then
- report("binary is not permitted: %s",program)
+ if validbinaries == true then
+ -- everything permitted
+ elseif not validbinaries[program] then
+ report("binary not permitted, ignoring command: %s",command)
+ return
+ elseif suspicious(command) then
+ report("/ \\ or .. found, ignoring command (use sandbox.registerrunner): %s",command)
return
end
- for i=0,#list do
- local n = list[i]
- if n then
- local v = readable(unquoted(n))
- if v then
- list[i] = optionalquoted(v)
- else
- report("suspicious argument found, run blocked: %s",n)
- return
- end
- end
+ return action(command,...)
+end
+
+-- local function binaryrunner(action,command,...)
+-- local original = command
+-- if validbinaries == false then
+-- -- nothing permitted
+-- report("no binaries permitted, ignoring command: %s",command)
+-- return
+-- end
+-- local program
+-- if type(command) == "table" then
+-- program = command[0]
+-- if program then
+-- command = concat(command," ",0)
+-- else
+-- program = command[1]
+-- if program then
+-- command = concat(command," ")
+-- end
+-- end
+-- elseif type(command) = "string" then
+-- program = lpegmatch(p_split,command)
+-- else
+-- report("command should be a string or table")
+-- return
+-- end
+-- if not program or program == "" then
+-- report("unable to filter binary from command: %s",command)
+-- return
+-- end
+-- if validbinaries == true then
+-- -- everything permitted
+-- elseif not validbinaries[program] then
+-- report("binary not permitted, ignoring command: %s",command)
+-- return
+-- elseif find(command,"[/\\]") or find(command,"%.%.") then
+-- report("/ \\ or .. found, ignoring command (use sandbox.registerrunner): %s",command)
+-- return
+-- end
+-- return action(original,...)
+-- end
+
+local function dummyrunner(action,command,...)
+ if type(command) == "table" then
+ command = concat(command," ",command[0] and 0 or 1)
end
- return action(name)
+ report("ignoring command: %s",command)
end
sandbox.filehandlerone = filehandlerone
sandbox.filehandlertwo = filehandlertwo
sandbox.iohandler = iohandler
-sandbox.runhandler = runhandler
-sandbox.binaryhandler = binaryhandler
function sandbox.disablerunners()
- norunners = true
+ validbinaries = false
end
-local execute = sandbox.original(os.execute)
+function sandbox.disablelibraries()
+ validlibraries = false
+end
+
+if FFISUPPORTED and ffi then
+
+ function sandbox.disablelibraries()
+ validlibraries = false
+ for k, v in next, ffi do
+ if k ~= "gc" then
+ ffi[k] = nil
+ end
+ end
+ end
+
+ local load = ffi.load
+
+ if load then
+
+ local reported = { }
+
+ function ffi.load(name,...)
+ if validlibraries == false then
+ -- all blocked
+ elseif validlibraries == true then
+ -- all permitted
+ return load(name,...)
+ elseif validlibraries[name] then
+ -- 'name' permitted
+ return load(name,...)
+ else
+ -- 'name' not permitted
+ end
+ if not reported[name] then
+ report("using library %a is not permitted",name)
+ reported[name] = true
+ end
+ return nil
+ end
+
+ end
-function sandbox.run(name,specification)
- return runhandler(execute,name,specification)
end
-------------------
@@ -360,16 +600,18 @@ local register = sandbox.register
if io then
overload(io.open, filehandlerone,"io.open")
- overload(io.popen, filehandlerone,"io.popen")
+ overload(io.popen, binaryrunner, "io.popen")
overload(io.input, iohandler, "io.input")
overload(io.output, iohandler, "io.output")
overload(io.lines, filehandlerone,"io.lines")
end
if os then
- overload(os.execute, binaryhandler, "os.execute")
- overload(os.spawn, binaryhandler, "os.spawn")
- overload(os.exec, binaryhandler, "os.exec")
+ overload(os.execute, binaryrunner, "os.execute")
+ overload(os.spawn, dummyrunner, "os.spawn")
+ overload(os.exec, dummyrunner, "os.exec")
+ overload(os.resultof, binaryrunner, "os.resultof")
+ overload(os.pipeto, binaryrunner, "os.pipeto")
overload(os.rename, filehandlertwo,"os.rename")
overload(os.remove, filehandlerone,"os.remove")
end
@@ -406,6 +648,11 @@ if epdf then
epdf.open = register(epdf.open, filehandlerone,"epdf.open")
end
+sandbox.registerroot = registerroot
+sandbox.registerbinary = registerbinary
+sandbox.registerlibrary = registerlibrary
+sandbox.validfilename = validfilename
+
-- not used in a normal mkiv run : os.spawn = os.execute
-- not used in a normal mkiv run : os.exec = os.exec