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

setfenv sandbox API

Started by hbomb79, 28 August 2015 - 09:57 PM
hbomb79 #1
Posted 28 August 2015 - 11:57 PM
Ok, so I have a program thats being run via this code:


local function wrapProgram( path )
   if runningProgram and coroutine.status( runningProgram ) == "suspended" then
	error("Thread already running on Stage.", 2)
   end
   if not path or type( path ) ~= "string" then
	error("new.runProgram: Expected: path String")
   end
   local func = false
   if getfenv and setfenv and type( getfenv ) == "function" and type( setfenv ) == "function" then
	local result, err = loadfile( path )
	if not result then
	 if err then
	  error("new.runProgram: "..tostring( err ))
	 else
	  error("new.runProgram: Error while loading file.")
	 end
	elseif result then
	 func = result
	 setfenv( func, sandbox )
	end
	if func then
	 runningProgram = coroutine.create(func)
	 pgPath = path
	else
	 error("new.runProgram: Could not load file")
	end
   end
  end

The coroutine created is run by a loop elsewhere, which isn't super important. My problem is this:

Say that the "path" is to a file that loads an API, and this API prints and effects the terminal, my sandbox prevents the path file from doing this, although the API simply bypasses it. This is both a huge security problem and means people running their program using my sandbox cannot use APIs, which is crazy.

Example:

--# file loaded
print("hey") --# blocked
os.loadAPI("myPrintingAPI")
myPrintingAPI.prnit("hey") --# is NOT blocked.
--# I need ANY and ALL attempts to draw to the screen to be captured so my framework can process them.

Some help with this would be great, it appears I would need to set the functions in the global space which raises heaps of other problems, because this is object oriented and each stage needs the functions object set, setting the functions in the global space gets messy pretty quick and can probably be overridden (maybe? I dont really know)… setfenv doesn't seem to affect loaded APIs at all.

Thanks in advance!
Edited on 28 August 2015 - 10:07 PM
KingofGamesYami #2
Posted 29 August 2015 - 12:20 AM
Well, the easiest way to sandbox drawing methods is to simply use a custom term object. No need to mess with environments.
hbomb79 #3
Posted 29 August 2015 - 12:35 AM
The framework is windowed, and I don't know how to stop the program from writing to another window:

Lets say window 1 is running program 1, and window 2 is running program 2.*both* programs will write to the active window (either window 1 or 2) not their own.

I would like to get environments down correctly so I can override other functions like os.shutdown (just close the window) and os.reboot(reopen window).

If I can get sandboxes working it would be much more beneficial to me, although this problem has really got me stumped, another issue that popped up is that the program cannot use the shell API which causes more problems.

Ideal Solution:
A sandbox that overrides os, term and somehow allows use of shell in the main program.
the term, os, shell etc… apis are overridden in APIs loaded by OR before the program, so any APIs that are being used by the sandboxed program.

If someone loaded their custom API and the framework, I need framework to override their drawing API only if its being called by the program, if its being called from outside the sandbox then just leave it be.


os.loadAPI("my_api")
os.loadAPI("myframework")
--# ...
my_api.write("hey") --#works
myframework.wrapProgram("test") --# my_api.write is caught in the program and processed
my_api.write("but this still works!")

I would rather try to understand sandboxes than take the easy way out at limit the functionality by using term.redirect
Edited on 28 August 2015 - 10:39 PM
KingofGamesYami #4
Posted 29 August 2015 - 01:19 AM
Well, here's the issue you're going to run into: The other API is loaded in an environment you do not have control over, because it uses the currently running programs environment. So, any changes to the program's environment (the one that calls wrapProgram) will be mirrored in the APIs environment. But, changes to the programs environment (the wrapped one) will not change the environment for the API.

TL;DR:
  • The origin program's environment == APIs environment
  • Changes to the wrapped programs environment will not affect the APIs, given that it was loaded by the origin program
  • Solution: Don't load APIs by the origin program!
hbomb79 #5
Posted 29 August 2015 - 01:28 AM
How do I avoid loading APIs via the framework, this is meant to be used by anyone, if their program requires an API then what shall I do?

There must be a way of doing it, OneOS and other programs have sandboxes that don't have this issue.

Do you have any ideas on how to change the environment of the running program, I thought sandboxing the function that calls the program would have changed the programs environment and reflect on any APIs loaded inside the sandbox.

Bear in mind, the framework is not loading APIs, just the program it is being instructed to run in a sandbox(window)

Main file (startup)

--# this is the main file
os.loadAPI("framework")
os.loadAPI("_framework") --# this is not loaded inside the framework OR the sandbox, this will not be blocked either
framework.run("test.lua") --# inside the framework, calls the wrapProgram(path) function.

This is the file that the startup file is running in sandbox via api framework

--# test.lua, loaded in sandbox
print("hey") --# this is caught
os.loadAPI("printer") --# this is being loaded inside the sandbox
printer.print("This is not caught")
_framework.print("This is not caught either")
--# so bascially, any API loaded AT ALL will escape the sandbox, whether its before, during (inside the sanboxed program) or after.

I will either have to give up on this project or someone needs to provide some example code, I have seen many sandboxes that function, but the source code is often too difficult to understand(looking at you OneOS)

Any help would be really appreciated
Edited on 28 August 2015 - 11:29 PM
KingofGamesYami #6
Posted 29 August 2015 - 01:35 AM
Make your own os.loadAPI function that includes your sandbox in the environment.

Spoiler

function os.loadAPI( _sPath )
    local sName = fs.getName( _sPath )
    if tAPIsLoading[sName] == true then
        printError( "API "..sName.." is already being loaded" )
        return false
    end
    tAPIsLoading[sName] = true

    local tEnv = {}
    setmetatable( tEnv, { __index = _G } ) --#change _G to whatever you want to use
    local fnAPI, err = loadfile( _sPath, tEnv )
    if fnAPI then
        local ok, err = pcall( fnAPI )
        if not ok then
            printError( err )
            tAPIsLoading[sName] = nil
            return false
        end
    else
        printError( err )
        tAPIsLoading[sName] = nil
        return false
    end

    local tAPI = {}
    for k,v in pairs( tEnv ) do
        if k ~= "_ENV" then
            tAPI[k] =  v
        end
    end

    _G[sName] = tAPI    
    tAPIsLoading[sName] = nil
    return true
end
*note: This is pulled from the latest version of CC. It may not work on older versions, I don't know.
hbomb79 #7
Posted 29 August 2015 - 01:48 AM
Will this still work if the API is loaded before my framework?

I _was_ considering that, before I thought it wouldn't work if the API is loaded beforehand.

Also, where do I place this function. Do I put it inside the sandboxes OS override, or just out in the framework. Wouldn't you have to use "framework.os.loadAPI" or something similar?

I will try putting it inside the sandbox.os table.

Oops! Here is the full sandbox:

http://pastebin.com/YDPEjXMy
*Indentation screwed up on the forums

EDIT:

This function (edited _G = sandbox) works for apis loaded in the sandbox, if they are loaded before running the sandbox OR before loading the framework they remain unaffected.

Is there a way of getting the currently loaded APIs, and unloading/reloading them using the sandbox? I assume that would fix that problem.
Edited on 28 August 2015 - 11:48 PM
KingofGamesYami #8
Posted 29 August 2015 - 02:08 AM
There really isn't any way to detect APIs loaded before your own, the closest you can come is iterating through _G, looking at the tables there, and if they contain functions they're probably an API. Even given that, it's hard to determine where the API might be, for example "api" might be "api", or "/something/something/api".

If you globally define os.loadAPI in your API, framework.os.loadAPI will be nil, because you modify the os table (not _G).
hbomb79 #9
Posted 29 August 2015 - 02:14 AM
So is the way I have declared it correct, or should it be _G.os.loadAPI = newFunction() ?

If there really is no way of doing this using a sandbox (or sandbox alone) then how would I go about it using a new terminal object (multi tasked, so using coroutines). A semi detailed explanation may be all I require to understand it enough to give it a go.
Edited on 29 August 2015 - 12:16 AM
KingofGamesYami #10
Posted 29 August 2015 - 02:20 AM
The way you've declared it is correct.

Basically you'd use term.redirect to send all input to another, custom built, object. Said object's functions would be managed by your API.
hbomb79 #11
Posted 29 August 2015 - 02:30 AM
This framework (which I will now refer to as DynaCode) is a windowed framework, due to this each window (I assume) needs their own redirect. I don't want to be constantly redirecting every time I need to draw to these windows.

The last time I tried it, when ever I redirected any writes on a window would be redirected to the current window, which means serveral programs were outputting to one window instead of the one they were supposed to.

Would the solution really be a loop just redirecting to each window and leaving the active window constantly redirected?


function writeToWindows()
	for k, stage in ipairs(stages) do
		term.redirect(stage.obj)
		term.<blah>
		--# ...
	end
	--# keep the redirect on what? Should I revert it back to the default or leave it at whatever it happens to end on? I am not entirely sure how to capture the default term (term.native(), current?)
end

and just to clarify, defining the loadAPI function as _G.os.loadAPI is wrong and I should define it in the sandbox.os table?
Edited on 29 August 2015 - 12:42 AM
hbomb79 #12
Posted 29 August 2015 - 04:43 AM
Wont this cause problems (like programs in other multishell tabs/in parallel printing to the active term). The nice thing about the sandbox is that it fixed this problem, but it is also its problem.

I am not too sure what the best way to redirect the terms, the above loops looks like it may cause problems for other multishell/paralell functions also using term, is there a way to set the redirect for just one function, similar to a sandbox?
MKlegoman357 #13
Posted 29 August 2015 - 07:43 AM
Right before resuming each program's coroutine you would redirect the terminal output to that program's window. And before yielding your coroutine manager to pull events you would redirect the terminal back to the original object.
hbomb79 #14
Posted 29 August 2015 - 10:10 AM
Gotcha, I will do so and let you know how it goes. Thanks for all your help Yami and legoman!