summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2012-07-29 18:12:58 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2012-07-29 18:12:58 +0100
commitc9ec1fc955887b4c5eccdb62a250cd994b84abdd (patch)
treefae482b8b3056ad7ef2374247fa419cbea89e6a1
parent340c969dabb6d666d5d052ace26c9b656b7a9126 (diff)
downloadsupple-c9ec1fc955887b4c5eccdb62a250cd994b84abdd.tar.gz
SUPPLE: Everything to get basic sandboxing working
-rw-r--r--lib/supple.lua4
-rw-r--r--lib/supple/capi.c10
-rw-r--r--lib/supple/comms.lua125
-rw-r--r--lib/supple/host.lua76
-rw-r--r--lib/supple/objects.lua35
-rw-r--r--lib/supple/request.lua2
-rw-r--r--lib/supple/sandbox.lua44
-rw-r--r--test/test-supple.objects.lua5
-rw-r--r--test/test-supple.request.lua3
-rw-r--r--test/test-supple.sandbox.lua3
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 = {}