This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
HPWebcamAble's profile picture

Making Sandbox more Secure

Started by HPWebcamAble, 08 February 2016 - 03:19 AM
HPWebcamAble #1
Posted 08 February 2016 - 04:19 AM
I'm working on a sandbox. How can I make it more secure (AKA prevent a program from getting around the restrictions)?

What it prevents the program from doing right now:
  • Directly accessing fs.open and fs.delete (these are overridden to tweak how they work)
  • Accessing through _G (Which is also overriden for the program)
Important Note:
Currently, it only restricts deleting and opening files. Lets assume it is only supposed to do that.

Spoiler

local args = {...}
if not args[1] then
  printError("Usage: sandbox <program> <args ...>")
elseif not fs.exists(args[1]) then
  printError("That program doesn't exist")
end


local env = {}

local function copyTable(tbl,newTbl)
  for a,b in pairs(tbl) do
	if type( b ) == "table" then
	  if b == tbl then
		newTbl[a] = newTbl
	  else
		newTbl[a] = {}
		copyTable(b,newTbl[a])
	  end
	else
	  newTbl[a] = b
	end
  end
end


copyTable( _G , env )


env.shell = {}
copyTable( shell , env.shell )
env.shell.getRunningProgram = function() return args[1] end

env["_G"] = env



env.fs.open = function(path,mode)
  print("Allow "..args[1].." to access "..path.."?")
  print("Y/N")
  while true do
	local event = {os.pullEvent("key")}
	if event[2] == keys.y then
	  return _G.fs.open(path,mode)
	elseif event[2] == keys.n then
	  return setmetatable({},{__index = function() return false end})
	end
  end
end

env.fs.delete = function(path)
  print("Allow '"..args[1].."' to delete '"..path.."'?")
  print("Y/N")
  while true do
	local event = {os.pullEvent("key")}
	if event[2] == keys.y then
	  return _G.fs.delete(path)
	elseif event[2] == keys.n then
	  return false
	end
  end
end

os.run(env,args[1],table.unpack(args,2))
Edited on 08 February 2016 - 03:21 AM
valithor #2
Posted 08 February 2016 - 04:59 AM
Little bypass I can see with your system.

Say someone were to do something like:

os.queueEvent("key",keys.y,false)
fs.delete("somefile")

The pullEvent in your function would think the user pressed the key, but instead it is just grabbing the key the program qued. A thing you can do to fix this would be something like:

local randomNum = math.random(9999,1000000) --# Generating a random event name, so the program could not fake your special event

env.fos.open = function(path,mode)
  print("Allow "..args[1].." to access "..path.."?")
  print("Y/N")
  os.queueEvent(randomNum) 
  os.pullEvent(randomNum) --# will make sure there are no events in the queue, which would end up happening anyway if you were listening for input
  while true do
		local event = {os.pullEvent("key")}
		if event[2] == keys.y then
		  return _G.fs.open(path,mode)
		elseif event[2] == keys.n then
		  return setmetatable({},{__index = function() return false end})
		end
  end
end
HPWebcamAble #3
Posted 08 February 2016 - 05:10 AM
Little bypass I can see with your system.

Good point, I didn't even think about that. In the final version, it'll use a dialog window, but it could still be similarly bypassed by queuing a click in the right location.
Edited on 08 February 2016 - 04:10 AM
Bomb Bloke #4
Posted 08 February 2016 - 05:50 AM
The pullEvent in your function would think the user pressed the key, but instead it is just grabbing the key the program qued. A thing you can do to fix this would be something like:

You'd want to generate a new randomNum every time env.fos.open() is called, or else a coder could sniff the event "signature" by pulling it via some coroutine manipulation.

Although, that might be the complex way to go about it, when this is an option:

parallel.waitForAll(function() fs.delete("somefile") end, function() os.queueEvent("key",keys.y,false) end)
valithor #5
Posted 08 February 2016 - 03:28 PM
parallel.waitForAll(function() fs.delete("somefile") end, function() os.queueEvent("key",keys.y,false) end)

I had thought of something like that, which might lead to something like this:

env.coroutine.yield = function(...)
  local event = {coroutine,yield}
  while event[#event] == "Program Queued Event") do
	table.remove(event,#event)
  end
  return event
end

env.os.queueEvent = function(...)
  os.queueEvent(...,"Program Queued Event")
end

local randomNum = math.random(9999,1000000) --# Generating a random event name, so the program could not fake your special event

env.fos.open = function(path,mode)
  print("Allow "..args[1].." to access "..path.."?")
  print("Y/N")
  while true do
				local event = {os.pullEvent("key")}
				if not event[#event] == "Program Queued Event" then
				  if event[2] == keys.y then
					return _G.fs.open(path,mode)
				  elseif event[2] == keys.n then
					return setmetatable({},{__index = function() return false end})
				  end
				end
  end
end

The program would only behave different if it for some reason queued a event with "Program Queued Event" at the end, but even then that could be accounted for. Only events queued by the program would be tagged, and anything outside of the program wouldn't remove the things at the end.
Edited on 08 February 2016 - 02:38 PM
SquidDev #6
Posted 08 February 2016 - 03:55 PM
I haven't tested these, but I'm pretty sure they'd allow me to get the un-sandboxed environment:

local env = getfenv(5) -- Probably high enough to be in the shell somewhere
local env = getfenv(print)
local env = _ENV -- CC 1.75+

Then:

env.fs.delete("foobar")
Edited on 08 February 2016 - 02:56 PM
HPWebcamAble #7
Posted 08 February 2016 - 10:55 PM
You'd want to generate a new randomNum every time env.fos.open() is called, or else a coder could sniff the event "signature" by pulling it via some coroutine manipulation.
That's what I was thinking. Of course, it's pretty easy to generate a new number each time.


local env = getfenv(5) -- Probably high enough to be in the shell somewhere
local env = getfenv(print)
local env = _ENV -- CC 1.75+

_ENV.fs.delete doesn't bypass the sandbox, at least not in CCEmuRedux running CC 1.78

getfenv(5) and getfenv(print) do bypass it, but couldn't you just override them in the sandbox as well?
They'll be removed in the update to Lua 5.2 anyway.
Edited on 08 February 2016 - 09:56 PM
Bomb Bloke #8
Posted 09 February 2016 - 02:25 AM
I had thought of something like that, which might lead to something like this:

Unfortunately you can't do this:

env.os.queueEvent = function(...)
  os.queueEvent(...,"Program Queued Event")
end

It'll return only the first parameter + "PQE". This'd work:

env.os.queueEvent = function(...)
  arg[arg.n + 1] = "Program Queued Event"
  os.queueEvent(unpack(arg))
end

… unless the coder did:

os.queueEvent("key",keys.y,false,nil)

… which'd cause unpack to trip on the nil…
valithor #9
Posted 09 February 2016 - 02:40 AM
Unfortunately you can't do this:

Ehh thats right. Might be easier to just move the "Program Queued Event" to before the …

There really isn't any reason it needed to be at the end.
MentalHamburger #10
Posted 17 February 2016 - 08:46 AM
couldnt you just use

shell.run("rm program")

to get past deleting something
Edited on 17 February 2016 - 07:46 AM
valithor #11
Posted 17 February 2016 - 12:23 PM
couldnt you just use

shell.run("rm program")

to get past deleting something

That would just run a program that uses fs.delete, so it would also be restricted.
Quartz101 #12
Posted 23 February 2016 - 10:39 AM
couldnt you just use

shell.run("rm program")

to get past deleting something

You could just block shell.run
valithor #13
Posted 23 February 2016 - 12:38 PM
couldnt you just use

shell.run("rm program")

to get past deleting something

You could just block shell.run

But shell.run doesn't bypass anything…
HPWebcamAble #14
Posted 24 February 2016 - 01:52 AM
couldnt you just use

shell.run("rm program")

to get past deleting something

You could just block shell.run

As valithor mentioned just before your comment, the 'rm' program uses fs.delete, so it's already covered.
PixelToast #15
Posted 24 February 2016 - 05:03 AM
couldnt you just use

shell.run("rm program")

to get past deleting something

You could just block shell.run

As valithor mentioned just before your comment, the 'rm' program uses fs.delete, so it's already covered.

no, shell.run runs commands in the unsandboxed environment
HPWebcamAble #16
Posted 24 February 2016 - 11:20 PM
shell.run runs commands in the unsandboxed environment

I was actually thinking about that. I'll have to test it to make sure, but you're probably correct.
Modifying shell.run should fix that.