diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-29 18:12:58 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-29 18:12:58 +0100 |
commit | c9ec1fc955887b4c5eccdb62a250cd994b84abdd (patch) | |
tree | fae482b8b3056ad7ef2374247fa419cbea89e6a1 | |
parent | 340c969dabb6d666d5d052ace26c9b656b7a9126 (diff) | |
download | supple-c9ec1fc955887b4c5eccdb62a250cd994b84abdd.tar.gz |
SUPPLE: Everything to get basic sandboxing working
-rw-r--r-- | lib/supple.lua | 4 | ||||
-rw-r--r-- | lib/supple/capi.c | 10 | ||||
-rw-r--r-- | lib/supple/comms.lua | 125 | ||||
-rw-r--r-- | lib/supple/host.lua | 76 | ||||
-rw-r--r-- | lib/supple/objects.lua | 35 | ||||
-rw-r--r-- | lib/supple/request.lua | 2 | ||||
-rw-r--r-- | lib/supple/sandbox.lua | 44 | ||||
-rw-r--r-- | test/test-supple.objects.lua | 5 | ||||
-rw-r--r-- | test/test-supple.request.lua | 3 | ||||
-rw-r--r-- | test/test-supple.sandbox.lua | 3 |
10 files changed, 282 insertions, 25 deletions
diff --git a/lib/supple.lua b/lib/supple.lua index e74bfa0..9cb2d8a 100644 --- a/lib/supple.lua +++ b/lib/supple.lua @@ -10,7 +10,9 @@ local capi = require 'supple.capi' local request = require 'supple.request' local objects = require 'supple.objects' +local comms = require 'supple.comms' local sandbox = require 'supple.sandbox' +local host = require 'supple.host' local _VERSION = 1 local _ABI = 1 @@ -21,7 +23,9 @@ return { capi = capi, request = request, objects = objects, + comms = comms, sandbox = sandbox, + host = host, _VERSION = _VERSION, VERSION = VERSION, _ABI = _ABI, diff --git a/lib/supple/capi.c b/lib/supple/capi.c index c0f8f48..0eaa372 100644 --- a/lib/supple/capi.c +++ b/lib/supple/capi.c @@ -244,6 +244,15 @@ supple_capi_lockdown(lua_State *L) return 2; } +static int +supple_capi_raw_getmm(lua_State *L) +{ + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + return 1; +} + static const struct luaL_Reg supple_capi_functions[] = { { "explain", supple_capi_explain }, @@ -251,6 +260,7 @@ supple_capi_functions[] = { { "type", supple_capi_type }, { "rawtype", supple_capi_rawtype }, { "lockdown", supple_capi_lockdown }, + { "raw_getmm", supple_capi_raw_getmm }, { NULL, NULL } }; diff --git a/lib/supple/comms.lua b/lib/supple/comms.lua new file mode 100644 index 0000000..efe737f --- /dev/null +++ b/lib/supple/comms.lua @@ -0,0 +1,125 @@ +-- lib/supple/comms.lua +-- +-- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine +-- +-- Management of communications between host and sandbox +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- 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 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 wait_for_response() + local back = request.deserialise(recv_msg()) + -- back could be three things + -- an error (raise it) + if back.error then + error(back.message .. "\n" .. back.traceback) + 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 = pcall(fn) + local resp + if not ok then + resp = request.error(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 + -- And try again + return wait_for_response() +end + +local function make_call(object, method, ...) + local req = request.request(object, method, ...) + send_msg(req) + return wait_for_response() +end + +return { + call = make_call, + _wait = wait_for_response, + _set_fd = set_fd, +}
\ No newline at end of file diff --git a/lib/supple/host.lua b/lib/supple/host.lua new file mode 100644 index 0000000..1226d19 --- /dev/null +++ b/lib/supple/host.lua @@ -0,0 +1,76 @@ +-- lib/supple/host.lua +-- +-- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine +-- +-- Management of the host side of Supple +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- For licence terms, see COPYING +-- + +local luxio = require 'luxio' +local subprocess = require 'luxio.subprocess' + +local comms = require 'supple.comms' +local objects = require 'supple.objects' + +local counter = 0 + +local function run_wrapper() + local wrapperpath = "@@WRAPPER_BIN@@" + -- START_TEST_SUPPLE + wrapperpath = "./testwrapper" + -- 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 = fds[1], +-- stderr = fds[1], + } + if not proc then + error(msg) + end + luxio.close(fds[1]) + return proc, fds[2] +end + +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(("host-%d"):format(counter)) + comms._set_fd(commsfd) + objects.set_proc_call(comms.call) + + local func, err = comms.call("supple:loadstring", "__call", codestr, codename) + if not func then + error(err) + end + + local ret = {func(...)} + + -- We need to clean up, so dump all the objects + func = nil + objects.clean_down() + + comms._set_fd(-1) + luxio.kill(child.pid, luxio.SIGKILL) + child:wait() + return unpack(ret) +end + +return { + run = run_sandbox, +}
\ No newline at end of file diff --git a/lib/supple/objects.lua b/lib/supple/objects.lua index 6c28cae..78f5788 100644 --- a/lib/supple/objects.lua +++ b/lib/supple/objects.lua @@ -9,6 +9,8 @@ -- For licence terms, see COPYING -- +local gc = collectgarbage + local capi = require 'supple.capi' local my_objects_by_obj = {} @@ -21,10 +23,24 @@ local proc_call = nil local type = capi.rawtype +local function clean_down() + -- And force a full GC + gc "collect" + gc "collect" + gc "collect" + -- And forget all our local objects + my_objects_by_obj = {} + my_objects_by_tag = {} +end + local function set_name(newname) my_name = newname end +local function get_name() + return my_name +end + local function set_proc_call(pc) proc_call = pc end @@ -36,7 +52,7 @@ local integral = { number = true, } -local function give(obj) +local function give(obj, special_tag) -- If the object is integral, return it directly if integral[type(obj)] then return obj @@ -54,7 +70,9 @@ local function give(obj) return { tag = tag } end -- otherwise wrap it freshly for us and return that. - local tag = ("%s:%d"):format(my_name, my_counter) + local tag = (special_tag and special_tag or + ("%s:%d"):format(my_name, my_counter)) + my_counter = my_counter + 1 local expn = capi.explain(obj, tag) my_objects_by_obj[obj] = tag my_objects_by_tag[tag] = obj @@ -83,6 +101,7 @@ local function receive(obj) -- Okay, prepare a proxy? assert(type(obj.type) == "string") local proxy, mt = capi.new_proxy(obj.type) + assert(capi.type(proxy) == obj.type) their_objects_by_tag[tag] = proxy their_objects_by_obj[proxy] = tag -- Fill out the metatable @@ -92,11 +111,12 @@ local function receive(obj) obj.methods[#obj.methods+1] = "__call" end if obj.type == "table" then + obj.methods[#obj.methods+1] = "__len" obj.methods[#obj.methods+1] = "__index" obj.methods[#obj.methods+1] = "__newindex" end for _, name in ipairs(obj.methods or {}) do - local function meta_func(...) + local function meta_func(mobj, ...) return proc_call(tag, name, ...) end mt[name] = meta_func @@ -105,9 +125,18 @@ local function receive(obj) return proxy end +local function forget_mine(tag) + local obj = my_objects_by_tag[tag] + my_objects_by_tag[tag] = nil + my_objects_by_obj[obj] = nil +end + return { set_name = set_name, + get_name = get_name, set_proc_call = set_proc_call, give = give, receive = receive, + clean_down = clean_down, + forget_mine = forget_mine, } diff --git a/lib/supple/request.lua b/lib/supple/request.lua index ab8388a..97f8f4f 100644 --- a/lib/supple/request.lua +++ b/lib/supple/request.lua @@ -22,7 +22,7 @@ local setfenv = setfenv local function serialise_error(errstr, traceback) return tconcat { "error=true,", - ("message=%q,"):format(errstr), + ("message=%q,"):format(("%s: %s"):format(objects.get_name(), errstr)), ("traceback=%q"):format(traceback) } end diff --git a/lib/supple/sandbox.lua b/lib/supple/sandbox.lua index 6b2d3ab..f096cb6 100644 --- a/lib/supple/sandbox.lua +++ b/lib/supple/sandbox.lua @@ -20,12 +20,14 @@ -- 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 type = type -- Run fn with globs as its globals. Returns a function to run which -- returns the return values of fn, and also wrap returns the table @@ -38,16 +40,16 @@ local type = type -- -- In case of error, returns nil, errmsg 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?") - globs = globs or {} if setfenv then -- Lua 5.1 style load... - fn_ret, msg = ((type(fn) == "string") and loadstring or load)(fn, src) + fn_ret, msg = ((capi.rawtype(fn) == "string") and loadstring or load)(fn, src) if not fn_ret then return nil, msg end @@ -65,14 +67,6 @@ local function _wrap(fn, src, globs) return fn_ret, fn_glob end -local function sandboxed_go() - -- Remove ourselves from the globals table so we cannot - -- be reentered - go = nil; - --- return io.receive() - return 0 -end local function run() -- Run the sandbox @@ -103,24 +97,40 @@ local function run() -- END_TEST_ONLY -- Prepare a severely limited sandbox - local sandbox_globals = {} + local sandbox_globals = { + type = capi.type, + } for _, k in ipairs({ "table", "string", "pairs", "ipairs", "pcall", "xpcall", "unpack", "tostring", "tonumber", "math", - "type", "coroutine", "select", "error", "assert" }) do + "coroutine", "select", "error", "assert" }) do sandbox_globals[k] = _G[k] end -- Complete its "globals" sandbox_globals._G = sandbox_globals - -- And add in the magic function we need - sandbox_globals.go = sandboxed_go - local fn, globs = _wrap("return go()", "sandbox", sandbox_globals) + local _go_str = [[ + return ({...})[1]() + ]] + + local fn, globs = _wrap(_go_str, "sandbox", sandbox_globals) if not fn then return 1 end - return fn() + objects.set_name(("supple-sandbox[%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(wrappered_load, "supple:loadstring") + comms._set_fd(0) + + return fn(comms._wait) end return { diff --git a/test/test-supple.objects.lua b/test/test-supple.objects.lua index 829de47..3af87a7 100644 --- a/test/test-supple.objects.lua +++ b/test/test-supple.objects.lua @@ -87,8 +87,7 @@ function suite.receive_function() assert(type(last_cb) == "table", "Call didn't reach callback") assert(last_cb.tag == unique_id, "Tag not propagated") assert(last_cb.name == "__call", "__call not propagated") - assert(last_cb.args[1] == obj, "obj not propagated") - assert(last_cb.args[2] == "fish", "args not propagated") + assert(last_cb.args[1] == "fish", "args not propagated") local obj2 = objects.receive { tag = unique_id } @@ -136,7 +135,7 @@ function suite.receive_table() assert(capi.type(obj) == "table", "Not a proxied function") assert(obj.thingy == last_cb, "Didn't proxy index") assert(last_cb.name == "__index", "didn't proxy") - assert(last_cb.args[2] == "thingy", "proxy wasn't right") + assert(last_cb.args[1] == "thingy", "proxy wasn't right") end function suite.received_object_gc() diff --git a/test/test-supple.request.lua b/test/test-supple.request.lua index 6ed5f5b..2ea7e66 100644 --- a/test/test-supple.request.lua +++ b/test/test-supple.request.lua @@ -32,8 +32,9 @@ end local suite = setmetatable({}, {__newindex = add_test}) function suite.serialise_error() + objects.set_name("n") local err = request.error("m","tb") - assert(err == [[error=true,message="m",traceback="tb"]], + assert(err == [[error=true,message="n: m",traceback="tb"]], "Error did not serialise properly") end diff --git a/test/test-supple.sandbox.lua b/test/test-supple.sandbox.lua index c75e6fc..06a9fd4 100644 --- a/test/test-supple.sandbox.lua +++ b/test/test-supple.sandbox.lua @@ -12,6 +12,9 @@ local luacov = require 'luacov' local sandbox = require 'supple.sandbox' +local comms = require 'supple.comms' + +comms._wait = function() return 0 end local testnames = {} |