-- lib/supple/sandbox.lua -- -- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine -- -- Code which runs the core sandbox functionality of supple. -- -- This module runs in the sandbox interpreter which means some of the code -- runs with root access. As such, we minimise what can be done before -- we drop privileges. -- -- The wrapper used to run us already ensured that LUA_PATH etc are -- not set, so we don't have to worry about non-system-installed modules -- getting in our way. Once the supple libraries are loaded (which will -- include loading luxio etc) we're good to go and can ask the supple.capi -- module to lock us down. -- -- Copyright 2012 Daniel Silverstone -- -- For licence terms, see COPYING -- local capi = require 'supple.capi' local objects = require 'supple.objects' local comms = require 'supple.comms' local luxio = require 'luxio' local sio = require 'luxio.simple' local loadstring = loadstring local load = load local setfenv = setfenv local gc = collectgarbage local function set_limits(ltab) local count = ltab.count local memory = ltab.memory local limits = { count = count, memory = memory} if limits.memory then -- Bump memory limit by current usage to be kinder limits.memory = limits.memory + (gc "count") end if not limits.count and not limits.memory then return false, "Expected an opcode count or total memory limit" end comms.set_limits(limits) return true end local function _wrap(fn, src, globs) globs = globs or {} local fn_glob = setmetatable({}, { __index = globs, __metatable=true }) local fn_ret, msg assert(fn, "No function/source provided?") assert(src, "No source name provided?") if setfenv then -- Lua 5.1 style load... fn_ret, msg = ((capi.rawtype(fn) == "string") and loadstring or load)(fn, src) if not fn_ret then return nil, msg end setfenv(fn_ret, fn_glob) else -- Lua 5.2 style load... fn_ret, msg = load(fn, src, "t", fn_glob) if not fn_ret then return nil, msg end end assert(fn_ret, "Unusual, missing fn_ret now?") return fn_ret, fn_glob end local function run() -- Run the sandbox local result, errno = capi.lockdown() if result ~= "ok" and result ~= "OK" -- START_TEST_ONLY and result ~= "oknonroot" -- END_TEST_ONLY then -- Failure to sandbox, so abort print(result, luxio.strerror(errno)) return errno end if result == "ok" then -- Note, if result is "OK" then we're pretty hard jailed -- and cannot even do this test lest we get killed -- Check that we're definitely solidly jailed fh, errno = sio.open("testfile", "rw") if fh then fh:close() luxio.unlink("testfile") return 1 end end -- Prepare a severely limited sandbox local sandbox_globals = { type = capi.type, pairs = capi.pairs, ipairs = capi.ipairs, next = capi.next, } for _, k in ipairs({ "table", "string", "pcall", "xpcall", "unpack", "tostring", "tonumber", "math", "coroutine", "select", "error", "assert" }) do sandbox_globals[k] = _G[k] end -- Complete its "globals" sandbox_globals._G = sandbox_globals local _go_str = [[ return ({...})[1]() ]] local fn, globs = _wrap(_go_str, "sandbox", sandbox_globals) if not fn then return 1 end objects.set_name(("supple-sandbox[%d,%%d]"):format(luxio.getpid())) objects.set_proc_call(comms.call) local function wrappered_load(str, name) return _wrap(str, name, sandbox_globals) end -- Pretend we've "given" the host an object called 'supple:loadstring' -- which is the loadstring/load function objects.give(set_limits, "supple:set_limits") objects.give(wrappered_load, "supple:loadstring") objects.give(objects.clean_down, "supple:clean_down") comms._set_fd(0) return fn(comms._wait) end return { run = run, }