|
| 1 | +-- Illustration of simple sandboxing techniques that can be used in luaj. |
| 2 | +-- |
| 3 | +-- This sandboxing is done in lua. These same techniques are all |
| 4 | +-- possible directly from Java, as shown in /examples/jse/SampleSandboxed.java. |
| 5 | +-- |
| 6 | +-- The main goals of this sandbox are: |
| 7 | +-- * lightweight sandbox controlled by single lua script |
| 8 | +-- * use new globals per-script and leave out dangerous libraries |
| 9 | +-- * use debug hook functions with yield to limit lua scripts |
| 10 | +-- * use read-only tables to protect shared metatables |
| 11 | + |
| 12 | +-- Replace the string metatable with a read-only version. |
| 13 | +debug.setmetatable('', { |
| 14 | + __index = string, |
| 15 | + __newindex = function() error('table is read only') end, |
| 16 | + __metatable = false, |
| 17 | +}) |
| 18 | + |
| 19 | +-- Duplicate contents of a table. |
| 20 | +local function dup(table) |
| 21 | + local t = {} |
| 22 | + for k,v in pairs(table) do t[k] = v end |
| 23 | + return t |
| 24 | +end |
| 25 | + |
| 26 | +-- Produce a new user environment. |
| 27 | +-- Only a subset of functionality is exposed. |
| 28 | +-- Must not expose debug, luajava, or other easily abused functions. |
| 29 | +local function new_user_globals() |
| 30 | + local g = { |
| 31 | + print = print, |
| 32 | + pcall = pcall, |
| 33 | + xpcall = xpcall, |
| 34 | + pairs = pairs, |
| 35 | + ipairs = ipairs, |
| 36 | + getmetatable = getmetatable, |
| 37 | + setmetatable = setmetatable, |
| 38 | + load = load, |
| 39 | + package = { preload = {}, loaded = {}, }, |
| 40 | + table = dup(table), |
| 41 | + string = dup(string), |
| 42 | + math = dup(math), |
| 43 | + bit32 = dup(bit32), |
| 44 | + -- functions can also be customized here |
| 45 | + } |
| 46 | + g._G = g |
| 47 | + return g |
| 48 | +end |
| 49 | + |
| 50 | +-- Run a script in it's own user environment, |
| 51 | +-- and limit it to a certain number of cycles before abandoning it. |
| 52 | +local function run_user_script_in_sandbox(script) |
| 53 | + do |
| 54 | + -- load the chunk using the main globals for the environment |
| 55 | + -- initially so debug hooks will be usable in these threads. |
| 56 | + local chunk, err = _G.load(script, 'main', 't') |
| 57 | + if not chunk then |
| 58 | + print('error loading', err, script) |
| 59 | + return |
| 60 | + end |
| 61 | + |
| 62 | + -- set the user environment to user-specific globals. |
| 63 | + -- these must not contain debug, luajava, coroutines, or other |
| 64 | + -- dangerous functionality. |
| 65 | + local user_globals = new_user_globals() |
| 66 | + debug.setupvalue(chunk, 1, user_globals) |
| 67 | + |
| 68 | + -- run the thread for a specific number of cycles. |
| 69 | + -- when it yields out, abandon it. |
| 70 | + local thread = coroutine.create(chunk) |
| 71 | + local hook = function() coroutine.yield('resource used too many cycles') end |
| 72 | + debug.sethook(thread, hook, '', 40) |
| 73 | + local errhook = function(msg) print("in error hook", msg); return msg; end |
| 74 | + print(script, xpcall(coroutine.resume, errhook, thread)) |
| 75 | + end |
| 76 | + |
| 77 | + -- run garbage collection to clean up orphaned threads |
| 78 | + collectgarbage() |
| 79 | +end |
| 80 | + |
| 81 | +-- Tun various test scripts that should succeed. |
| 82 | +run_user_script_in_sandbox( "return 'foo'" ) |
| 83 | +run_user_script_in_sandbox( "return ('abc'):len()" ) |
| 84 | +run_user_script_in_sandbox( "return getmetatable('abc')" ) |
| 85 | +run_user_script_in_sandbox( "return getmetatable('abc').len" ) |
| 86 | +run_user_script_in_sandbox( "return getmetatable('abc').__index" ) |
| 87 | + |
| 88 | +-- Example user scripts that attempt rogue operations, and will fail. |
| 89 | +run_user_script_in_sandbox( "return setmetatable('abc', {})" ) |
| 90 | +run_user_script_in_sandbox( "getmetatable('abc').len = function() end" ) |
| 91 | +run_user_script_in_sandbox( "getmetatable('abc').__index = {}" ) |
| 92 | +run_user_script_in_sandbox( "getmetatable('abc').__index.x = 1" ) |
| 93 | +run_user_script_in_sandbox( "while true do print('loop') end" ) |
| 94 | + |
| 95 | +-- Example use of other shared metatables, which should also be made read-only. |
| 96 | +-- this toy example allows booleans to be added to numbers. |
| 97 | + |
| 98 | +-- Normally boolean cannot participate in arithmetic. |
| 99 | +local number_script = "return 2 + 7, 2 + true, false + 7" |
| 100 | +run_user_script_in_sandbox(number_script) |
| 101 | + |
| 102 | +-- Create a shared metatable that includes addition for booleans. |
| 103 | +-- This would only be set up by the server, not by client scripts. |
| 104 | +debug.setmetatable(true, { |
| 105 | + __newindex = function() error('table is read only') end, |
| 106 | + __metatable = false, |
| 107 | + __add = function(a, b) |
| 108 | + return (a == true and 1 or a == false and 0 or a) + |
| 109 | + (b == true and 1 or b == false and 0 or b) |
| 110 | + end |
| 111 | +}) |
| 112 | + |
| 113 | +-- All user scripts will now get addition involving booleans. |
| 114 | +run_user_script_in_sandbox(number_script) |
0 commit comments