summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2012-08-04 13:05:50 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2012-08-04 13:05:50 +0100
commitd73be348acdf9f18479734fd09ddbfdb017c47bf (patch)
treec16b7cf6336ab0293eec6257698cfda735cabc5c
parenta1547bb047dfe66cffa4811d05fb08d98c6d81c6 (diff)
downloadsupple-d73be348acdf9f18479734fd09ddbfdb017c47bf.tar.gz
SUPPLE: Basic instruction count and/or memory consumption limits for the sandbox
-rw-r--r--example/simple-example.lua16
-rw-r--r--lib/supple/comms.lua35
-rw-r--r--lib/supple/host.lua18
-rw-r--r--lib/supple/sandbox.lua27
4 files changed, 84 insertions, 12 deletions
diff --git a/example/simple-example.lua b/example/simple-example.lua
index af19499..c385474 100644
--- a/example/simple-example.lua
+++ b/example/simple-example.lua
@@ -67,7 +67,7 @@ lprint(supple.host.run("local f = ... f()", "@test-code", function() unknown() e
-- And now, one where we pass an error from sandbox to host to sandbox and back
lprint(supple.host.run("local f = ... f(function() unknown() end)", "@test-code", function(ff) ff() end))
--- And finally, a reasonable traceback via named functions on each end...
+-- Next , a reasonable traceback via named functions on each end...
local errsrc = [[
function raises()
@@ -93,3 +93,17 @@ end
lprint(supple.host.run(errsrc, "@error-code", loopback))
+-- Now we try the sandboxing limits
+
+local long_run = [[
+ local s = ""
+ for i = 1, 10000 do
+ s = s .. "FISHFISHFISHFISHFISH"
+ end
+ return #s
+]]
+
+supple.host.set_limits { count = 100 }
+lprint(supple.host.run(long_run, "@long-code"))
+supple.host.set_limits { memory = 1000 }
+lprint(supple.host.run(long_run, "@big-code"))
diff --git a/lib/supple/comms.lua b/lib/supple/comms.lua
index d175bff..ea80fad 100644
--- a/lib/supple/comms.lua
+++ b/lib/supple/comms.lua
@@ -21,6 +21,7 @@ local error = error
local getinfo = debug.getinfo
local concat = table.concat
local xpcall = xpcall
+local gc = collectgarbage
local fd = -1
@@ -77,6 +78,32 @@ local function captcha(msg)
return {msg, concat(traceback, "\n") or ""}
end
+local limits = {}
+local count_per_hook = 10
+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())
@@ -150,8 +177,16 @@ local function make_call(object, method, ...)
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,
} \ No newline at end of file
diff --git a/lib/supple/host.lua b/lib/supple/host.lua
index f0181fa..bac95ae 100644
--- a/lib/supple/host.lua
+++ b/lib/supple/host.lua
@@ -17,6 +17,7 @@ local objects = require 'supple.objects'
local hostname = "host"
local counter = 0
+local limits
local function run_wrapper()
local wrapperpath = "@@WRAPPER_BIN@@"
@@ -63,12 +64,22 @@ local function run_sandbox(codestr, codename, ...)
-- which we do not need to fill out.
err = nil
+ 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 = pcall(capture)
+ local ok
+ if limitsok then
+ ok, err = pcall(capture)
+ else
+ ok, err = limitsok, limitserr
+ end
-- We need to clean up, so dump all the objects
func = nil
-- And ask the supple API to clear down too
@@ -89,7 +100,12 @@ local function set_hostname(newname)
counter = 0
end
+local function set_limits(newlimits)
+ limits = newlimits
+end
+
return {
run = run_sandbox,
set_name = set_hostname,
+ set_limits = set_limits,
} \ No newline at end of file
diff --git a/lib/supple/sandbox.lua b/lib/supple/sandbox.lua
index 3c807cb..57e5cd3 100644
--- a/lib/supple/sandbox.lua
+++ b/lib/supple/sandbox.lua
@@ -28,17 +28,23 @@ local sio = require 'luxio.simple'
local loadstring = loadstring
local load = load
local setfenv = setfenv
+local gc = collectgarbage
+
+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
--- 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
--- which will be filled with any new globals fn creates.
---
--- If fn is a string then it is used as the source of the function, if it
--- is a function then it is expected to be a closure which when called
--- repeatedly returns more data loaded (perhaps from a file?). src is
--- expected to be the name associated with this source code.
---
--- In case of error, returns nil, errmsg
local function _wrap(fn, src, globs)
globs = globs or {}
local fn_glob = setmetatable({}, { __index = globs, __metatable=true })
@@ -127,6 +133,7 @@ local function run()
-- Pretend we've "given" the host an object called 'supple:loadstring'
-- which is the loadstring/load function
+ 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)