John Smith
John Smith

Reputation: 12797

Lua Sandbox with special functions which leak

I am trying to use How can I create a secure Lua sandbox? to build my own leaky sandbox.

I am trying to create a Lua sandbox where some Lua functions can access some other Lua functions outside the sandbox. For example I want my sandbox to have a special "display" function which can call "print" but not have "print" in the sandbox too.

The main problem is that I am trying to build a sandbox within an already large codebase, so I cannot nil away functions.

How is this possible?

The solution has to be a pure Lua function due to no fault of mine.

Upvotes: 4

Views: 1724

Answers (3)

BMitch
BMitch

Reputation: 263509

Build your sandbox (or multiple sandboxes if they each have different requirements) and move the untrusted code into the sandbox one piece at a time. In my quick cli tests, both 5.1 and 5.2 will run functions that were defined outside of the sandbox without modification. To use Doug's example, assume display is part of your pre-existing code that uses print:

-- 5.1
local function display(...)
  print(...)
end
local script = loadstring "display(math.log(2, 3))"
local env = {display = display, math = math, string = string}
setfenv(script, env)
print(pcall(script))

-- 5.2
local function display(...)
  print(...)
end
local script = loadstring "display(math.log(2, 3))"
local e=_ENV
_ENV={display = display, math = math, string = string}
e.print(e.pcall(script))
_ENV=e

Note that in both of the above examples, the display function is using print without modification to that code since you were not in the sandbox when this function was created.

In the past, I've stored a local pointer to the un-sandboxed environment, but I can't reproduce the situation where that's needed in my quick cli tests. If you can come up with an example, I can probably come up with a workaround that doesn't require the e variable. Here's an example of that code using 5.2:

local e=_ENV

for k,v in e.pairs(value) do
-- iterate
end

another example, for my read only table code, I'm again using the e:

function ro_table (t)
  local t = t
  if t then
    return e.setmetatable({}, 
      { __index=t, 
        __newindex= function(_,_,_) e.error ("Attempt to modify read-only table") end, 
      })
  else
    return nil
  end
end

Upvotes: 0

Doug Currie
Doug Currie

Reputation: 41170

When you create a sandbox, you do it by cherry picking functions and values from a larger environment to create a new sandbox environment. You do not need to destroy or "nil out" anything in the original environment.

  1. Create your sandbox environment by cherry picking functions and values
  2. Load a script (this compiles it and returns it as a function to call)
  3. Set the environment of the script to the sandbox environment
  4. Execute the script in the sandbox

So,

local script = loadstring "display(math.log(2, 3))"
local env = {display = print, math = math, string = string}
setfenv(script, env)
pcall(script)

prints

0.69314718055995

whereas

local script = loadstring "print(math.log(2, 3))"
local env = {display = print, math = math, string = string}
setfenv(script, env)
pcall(script)

fails with

false   [string "print(math.log(2, 3))"]:1: attempt to call global 'print' (a nil value)

Upvotes: 5

Nicol Bolas
Nicol Bolas

Reputation: 473272

Does it specifically need to call the Lua standard library print function? Can you instead emulate the functionality of print? Because that would be the easiest way.

However, if you want to have a wrapper around print, there are two ways to do it: with pure Lua code, and with C/C++ code.

The pure Lua solution is as follows. Note that this should be done before loading any external scripts. First, open the Lua standard library that has print in it. Then run this Lua script:

local internal_print = print

return function(...)
    --Do display logic.
    internal_print(...) --Or whatever else you want.
end

This will return the "display" function. You can store it in a global variable called display if you like, or called something else.

After that, you can nil out the print global variable, thus making it almost entirely inaccessible.

If you want to do it from C/C++, it's very similar. First, as before, you register the Lua standard library that includes print, so that you can get the function for it. Then, you use lua_getglobal(L, "print") to get the print function and push it onto the stack. Next, you register your C/C++ function using using lua_pushcclosure. But you want to specify one upvalue, which Lua pops off the stack at registration time.

And now your registered function is on the stack, waiting to be pushed into a Lua variable or global table entry.

Warning: the Lua debug library can poke at upvalues and thus get the print function from your new function. So if you want perfect security, get rid of debug.getupvalue.

Upvotes: 1

Related Questions