957 posts
Location
Web Development
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
1023 posts
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
957 posts
Location
Web Development
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
7083 posts
Location
Tasmania (AU)
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)
1023 posts
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
1426 posts
Location
Does anyone put something serious here?
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
957 posts
Location
Web Development
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
7083 posts
Location
Tasmania (AU)
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…
1023 posts
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.
12 posts
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
1023 posts
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.
149 posts
Location
/dev/nvme0n1
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
1023 posts
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…
957 posts
Location
Web Development
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.
2217 posts
Location
3232235883
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
957 posts
Location
Web Development
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.