summaryrefslogtreecommitdiff
path: root/lib/supple/sandbox.lua
blob: ab1d87a3e5a5841ab5d1e8bb1669c5f194e8f36b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
-- lib/supple/sandbox.lua
--
-- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine
--
-- Code which runs the core sandbox functionality of supple.
--
-- This module runs in the sandbox interpreter which means some of the code
-- runs with root access.  As such, we minimise what can be done before
-- we drop privileges.
--
-- The wrapper used to run us already ensured that LUA_PATH etc are
-- not set, so we don't have to worry about non-system-installed modules
-- getting in our way.  Once the supple libraries are loaded (which will
-- include loading luxio etc) we're good to go and can ask the supple.capi
-- module to lock us down.
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For licence terms, see COPYING
--

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 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

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?")

   if setfenv then
      -- Lua 5.1 style load...
      fn_ret, msg = ((capi.rawtype(fn) == "string") and loadstring or load)(fn, src)
      if not fn_ret then
	 return nil, msg
      end
      setfenv(fn_ret, fn_glob)
   else
      -- Lua 5.2 style load...
      fn_ret, msg = load(fn, src, "t", fn_glob)
      if not fn_ret then
	 return nil, msg
      end
   end

   assert(fn_ret, "Unusual, missing fn_ret now?")

   return fn_ret, fn_glob
end


local function run()
   -- Run the sandbox
   local result, errno = capi.lockdown()

   if result ~= "ok" and result ~= "OK"
-- START_TEST_ONLY
      and result ~= "oknonroot"
-- END_TEST_ONLY
   then
      -- Failure to sandbox, so abort
      print(result, luxio.strerror(errno))
      return errno
   end

   if result == "ok" then
      -- Note, if result is "OK" then we're pretty hard jailed
      -- and cannot even do this test lest we get killed
      -- Check that we're definitely solidly jailed
      fh, errno = sio.open("testfile", "rw")
      if fh then
	 fh:close()
	 luxio.unlink("testfile")
	 return 1
      end
   end
   
   -- Prepare a severely limited sandbox
   local sandbox_globals = {
      type = capi.type,
      pairs = capi.pairs,
      ipairs = capi.ipairs,
      next = capi.next,
   }

   for _, k in ipairs({ "table", "string", "pcall",
			 "xpcall", "unpack", "tostring", "tonumber", "math",
			 "coroutine", "select", "error", "assert" }) do
      sandbox_globals[k] = _G[k]
   end
   -- Complete its "globals"
   sandbox_globals._G = sandbox_globals

   local _go_str = [[
	 return ({...})[1]()
   ]]

   local fn, globs = _wrap(_go_str, "sandbox", sandbox_globals)
   if not fn then
      return 1
   end

   objects.set_name(("supple-sandbox[%d,%%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(set_limits, "supple:set_limits")
   objects.give(wrappered_load, "supple:loadstring")
   objects.give(objects.clean_down, "supple:clean_down")
   comms._set_fd(0)

   return fn(comms._wait)
end

return {
   run = run,
}