-- 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 track = require 'supple.track' local loadstring = loadstring local load = load local setfenv = setfenv local gc = collectgarbage local unpack = unpack 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, dont_wrap_globs) local fn_ret, msg if not dont_wrap_globs then globs = setmetatable({}, { __index = globs, __metatable = true }) end 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, globs) else -- Lua 5.2 style load... fn_ret, msg = load(fn, src, "t", globs) if not fn_ret then return nil, msg end end assert(fn_ret, "Unusual, missing fn_ret now?") return fn_ret, globs end local function wrapped_unpack(t) local packed = t if capi.rawtype(t) ~= "table" then local len = #t packed = {} for i = 1, len do packed[i] = t[i] end end return unpack(packed) 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, unpack = wrapped_unpack, } for _, k in ipairs({ "table", "string", "pcall", "xpcall", "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 dont_wrap_globs = true local function wrappered_load(str, name) local dwg = dont_wrap_globs dont_wrap_globs = false return _wrap(str, name, sandbox_globals, dwg) end -- Pretend we've "given" the host an object called 'supple:loadstring' -- which is the loadstring/load function track.start() 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, }