-- lib/supple/host.lua -- -- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine -- -- Management of the host side of Supple -- -- Copyright 2012 Daniel Silverstone -- -- For licence terms, see COPYING -- --- Running the host-side of sandboxed code -- -- When running code in Supple sandboxes, the starting point is this module. -- The host application should require supple and then begin from here... -- -- local supple = require 'supple' -- print(supple.sandbox.run("return ...", "example code", "arg1", "arg2")) -- -- Expect: 'arg1 arg2' as output -- local luxio = require 'luxio' local subprocess = require 'luxio.subprocess' local comms = require 'supple.comms' local objects = require 'supple.objects' local capi = require 'supple.capi' local track = require 'supple.track' local hostname = "host" local counter = 0 local limits, globals local function simplify(t, memo) if not memo then memo = {} end if memo[t] then return memo[t] end local ret = {} memo[t] = ret local kk, vv for k, v in capi.pairs(t) do kk, vv = k, v if capi.type(k) == "table" then kk = simplify(k, memo) end if capi.type(v) == "table" then vv = simplify(v, memo) end if capi.rawtype(kk) ~= "userdata" and capi.rawtype(vv) ~= "userdata" then -- We've not got a proxy left over anywhere, so copy it ret[kk] = vv end end return ret end local function run_wrapper() local wrapperpath = "@@WRAPPER_BIN@@" -- START_TEST_SUPPLE wrapperpath = os.getenv "SUPPLE_TEST_WRAPPER" -- END_TEST_SUPPLE local fds = {} local ret, errno = luxio.socketpair(luxio.AF_UNIX, luxio.SOCK_STREAM, luxio.PF_UNIX, fds) if ret ~= 0 then error("Unable to launch subprocess, could not prepare socketpair():" .. luxio.strerror(errno)) end local proc, msg = subprocess.spawn { "supple-sandbox", exe = wrapperpath, stdin = fds[1], stdout = -1, stderr = -1, close_in_child = { fds[1], fds[2] }, } if not proc then error(msg) end luxio.close(fds[1]) return proc, fds[2] end --- Run some code in a Supple sandbox -- -- Call this routine to run some code in a Supple sandbox. The code is -- compiled and run entirely within the sandbox process. Any kind of value -- can be passed to the code as an argument. Intrinsics are transferred -- immediately (numbers, booleans, nils, strings) but complex values (tables, -- userdata, and functions) are sent over as a proxy and actions on those -- values are proxied back to the host to be run. Users of Supple should be -- aware that those routines are therefore not sandboxed. -- -- @tparam string codestr The code to run in the sandbox, as a string. -- @tparam string codename The name to give to the code running in the sandbox. -- @param[opt] ... Arguments to pass to the code running in the sandbox. -- @function run local function run_sandbox(codestr, codename, ...) -- Prepare and start a sandbox, -- compiling the codestr and running it -- with the given args local child, commsfd = run_wrapper() counter = counter + 1 objects.set_name(("%s[%d,%%d]"):format(hostname,counter)) comms._set_fd(commsfd) objects.set_proc_call(comms.call) track.start() local func, env = comms.call("supple:loadstring", "__call", codestr, codename) if not func then error(env) end local limitsok, limitserr = true if limits then limitsok, limitserr = comms.call("supple:set_limits", "__call", limits) end -- In a protected manner, capture the output of the call local args, ret = {...} local function capture() ret = {func(unpack(args))} end local ok, err if limitsok then if globals then -- Hand over the globals for k, v in pairs(globals) do env[k] = v end end ok, err = pcall(capture) else ok, err = limitsok, limitserr end -- Convert any complex objects returned to us, so we can clean up... if ok then ret = simplify(ret) end -- We need to clean up, so dump all the objects func = nil env = nil -- And ask the supple API to clear down too objects.clean_down(true) comms._set_fd(-1) luxio.close(commsfd) child:wait() if ok then return ok, unpack(ret) else return ok, err .. "\n\nHost comms track:\n" .. track.stop() end end --- -- Load a string into the sandbox and return it as a wrappered function. -- -- This loads the given string (with given name) into the sandbox interpreter -- and then wrappers the function and returns it to the caller. This can then -- be used to call code inside the sandbox. This is the entry point to load -- further code into the sandbox and should only be called during running -- sandboxed code (i.e. `supple.host.run` is running) -- -- @tparam string codestr The code to be loaded in the sandboxed interpreter -- @tparam string codename The name to be given to the code when it is loaded -- @treturn function The wrappered function which can then be called or handed -- back to the sandbox as needed. -- @treturn table The function environment for the loaded sandboxed code. -- @function loadstring local function sandboxed_loadstring(codestr, codename) return comms.call("supple:loadstring", "__call", codestr, codename) end --- -- Set the name by which the host refers to itself in traces. -- -- Calling this resets the host sandbox counter and sets the name of the host -- to the given new name. This mostly has an effect on object names and trace -- data which typically only appear if the user of Supple chooses to trace the -- activity of the sandbox, or if an error occurs. -- -- @tparam string newname The new host name to set -- @function set_name local function set_hostname(newname) hostname = newname counter = 0 end --- -- Set a new limits table for use in a new sandbox instance. -- -- When a sandbox is run (from `supple.host.run`) it has a number of soft -- limits sent to it to be honoured during the runtime of the sandbox. -- Normally the sandbox limits itself only by hard limits, but this call allows -- tighter soft limits to be set if so-desired. -- -- The table provided takes the form: -- -- { [count = maxopcodestorun], [memory = maxbytestouse] } -- -- If you call this function then at least one, but optionally both of the -- count and memory table entries need to be present. -- -- @tparam table newlimits The new soft-limits for any new sandboxes -- @function set_limits local function set_limits(newlimits) limits = newlimits end --- -- Set a new globals table to be used by new sandbox runs. -- -- When a sandboxed function is run, it is run within a given globals table. -- The globals in question are provided by calling this function. Remember -- that the table will be passed *shallowly* and new globals created by the -- sandboxed code will not be copied back to the host. -- -- @tparam table newglobals The set of globals to be passed to the sandbox -- @function set_globals local function set_globals(newglobals) globals = newglobals end return { run = run_sandbox, loadstring = sandboxed_loadstring, set_name = set_hostname, set_limits = set_limits, set_globals = set_globals, }