-- lib/supple/comms.lua -- -- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine -- -- Management of communications between host and sandbox -- -- Copyright 2012 Daniel Silverstone -- -- For licence terms, see COPYING -- local luxio = require "luxio" local capi = require "supple.capi" local request = require "supple.request" local objects = require "supple.objects" local unpack = unpack local tonumber = tonumber local error = error local getinfo = debug.getinfo local concat = table.concat local xpcall = xpcall local gc = collectgarbage local fd = -1 local function set_fd(_fd) fd = _fd end local function send_msg(msg) if (#msg > 99999) then error("Message too long") end local msglen = ("%05d"):format(#msg) luxio.write(fd, msglen .. msg) end local function recv_msg() local len = luxio.read(fd, 5) if #len < 5 then error("Unable to read 5 byte length") end len = tonumber(len) if len == nil or len < 1 or len > 99999 then error("Odd, len didn't translate properly") end local str = luxio.read(fd, len) if type(str) ~= "string" or #str ~= len then error("Unable to read " .. tostring(len) .. " bytes of msg") end return str end local function captcha(msg) local traceback = {} local level = 2 local info = getinfo(level, "Snlf") local function in_supple() return info.short_src:match("/supple/[^%.]+%.lua$") end while info and info.func ~= xpcall do if info.currentline > 0 and not in_supple() then local ttype, tag = objects.find_tag(info.func) if ttype then info.name = tag end local line = ("\t%s:%d in function %s"):format(info.short_src, info.currentline, info.name or "") traceback[#traceback+1] = line end level = level + 1 info = getinfo(level, "Snlf") end return {msg, concat(traceback, "\n") or ""} end local limits = {} math.randomseed(os.time()) math.randomseed(math.random(luxio.getpid())) local count_per_hook = 10 + (math.random(5) - 3) local limiting = false local function limit_hook() local inside = getinfo(2, "Snlf") if inside.short_src == "[C]" or inside.name == "(tail call)" then return end if limiting and not inside.short_src:match("/supple/[^%.]+%.lua$") then if limits.count then limits.count = limits.count - count_per_hook if limits.count < 0 then limiting = false error("Used too many instructions", 2) end end if limits.memory then if limits.memory < gc "count" then limiting = false error("Exceeded memory usage limit", 2) end end end end local function wait_for_response() repeat local back = request.deserialise(recv_msg()) -- back could be three things -- an error (raise it) if back.error then local msg = back.message if back.traceback and back.traceback ~= "" then msg = msg .. "\n" .. back.traceback end error(msg, 2) end -- A result, return it if back.error == false then return unpack(back.results) end -- A method call, call it local function safe_method(fn) local ok, res = xpcall(fn, captcha) local resp if not ok then resp = request.error(unpack(res)) else resp = request.response(unpack(res)) end send_msg(resp) end if back.method == "__gc" then -- __gc is the garbage collect mechanism objects.forget_mine(back.object) send_msg(request.response()) elseif back.method == "__call" then -- __call is the function call mechanism safe_method(function() local obj = objects.receive { tag = back.object } return {obj(unpack(back.args))} end) elseif back.method == "__len" then safe_method(function() local obj = objects.receive { tag = back.object } return {#obj} end) elseif back.method == "__index" then safe_method(function() local obj = objects.receive { tag = back.object } return {obj[back.args[1]]} end) elseif back.method == "__newindex" then safe_method(function() local obj = objects.receive { tag = back.object } obj[back.args[1]] = back.args[2] return {} end) else safe_method(function() local obj = objects.receive { tag = back.object } local meth = capi.raw_getmm(obj, back.method) if not meth then error("Unknown or disallowed method: " .. back.method, "") end return {meth(obj, unpack(back.args))} end) end until false end local function make_call(object, method, ...) local req = request.request(object, method, ...) send_msg(req) return wait_for_response() end local function set_limits(newlimits) limits = newlimits debug.sethook() debug.sethook(limit_hook, "c", count_per_hook) limiting = true end return { call = make_call, _wait = wait_for_response, _set_fd = set_fd, set_limits = set_limits, }