[solved] Sandbox Env doesn't Recognize Functions

Post Reply
User avatar
octacian
Member
Posts: 597
Joined: Mon Dec 21, 2015 22:18
GitHub: octacian
IRC: octacian
In-game: octacian
Location: Canada

[solved] Sandbox Env doesn't Recognize Functions

by octacian » Post

In other words, when I try and run code in the sandbox, functions are undefined (or nil) causing the game to crash. Much of the code is based off that for the Mesecons Luacontroller, as that was the best I could find to help be understand how to use sandboxes to begin with. I'm not sure that the method I'm using is the best, as things are spread in between os.lua and api.lua. I was hoping to put everything there, but then the custom functions would be undefined when simply forming the env table. Anyways, here's what I came up with.

env table and custom functions (api.lua):

Code: Select all

-----------------
-- ENVIRONMENT --
-----------------

-- CUSTOM SAFE FUNCTIONS --

local function safe_print(param)
	print(dump(param))
end

local function safe_date()
	return(os.date("*t",os.time()))
end

-- string.rep(str, n) with a high value for n can be used to DoS
-- the server. Therefore, limit max. length of generated string.
local function safe_string_rep(str, n)
	if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
		debug.sethook() -- Clear hook
		error("string.rep: string length overflow", 2)
	end

	return string.rep(str, n)
end

-- string.find with a pattern can be used to DoS the server.
-- Therefore, limit string.find to patternless matching.
local function safe_string_find(...)
	if (select(4, ...)) ~= true then
		debug.sethook() -- Clear hook
		error("string.find: 'plain' (fourth parameter) must always be true for digicomputers.")
	end

	return string.find(...)
end

-- [function] set
local function set_string(key, value)
  return meta:set_string(key, value)
end
-- [function] get
local function get_string(key)
  return meta:get_string(key)
end
-- [function] set int
local function set_int(key, value)
  return meta:set_int(key, value)
end
-- [function] get int
local function get_int(key)
  return meta:get_int(key)
end
-- [function] set float
local function set_float(key, value)
  return meta:set_float(key, value)
end
-- [function] get float
local function get_float(key)
  return meta:get_float(key)
end
-- [function] get input
local function get_input()
  return meta:get_string("input")
end
-- [function] set input
local function set_input(value)
  return meta:set_string("input", value)
end
-- [function] get output
local function get_output()
  return meta:get_string("output")
end
-- [function] set output
local function set_output(value)
  return meta:set_string("output", value)
end
-- [function] get field
local function get_field(key)
  return field[key]
end
-- [function] refresh formspec
local function refresh()
  meta:set_string("formspec", digicompute.formspec(meta:get_string("input"), meta:get_string("output")))
end

-- env
local env = {
  run = digicompute.os.run,
  set_string = set_string,
  get_string = get_string,
  set_int = set_int,
  get_int = get_int,
  set_float = set_float,
  get_float = get_float,
  get_input = get_input,
  set_input = set_input,
  get_output = get_output,
  set_output = set_output,
  get_field = get_field,
  refresh = refresh,
  string = {
    byte = string.byte,
    char = string.char,
    format = string.format,
    len = string.len,
    lower = string.lower,
    upper = string.upper,
    rep = safe_string_rep,
    reverse = string.reverse,
    sub = string.sub,
    find = safe_string_find,
  },
  math = {
    abs = math.abs,
    acos = math.acos,
    asin = math.asin,
    atan = math.atan,
    atan2 = math.atan2,
    ceil = math.ceil,
    cos = math.cos,
    cosh = math.cosh,
    deg = math.deg,
    exp = math.exp,
    floor = math.floor,
    fmod = math.fmod,
    frexp = math.frexp,
    huge = math.huge,
    ldexp = math.ldexp,
    log = math.log,
    log10 = math.log10,
    max = math.max,
    min = math.min,
    modf = math.modf,
    pi = math.pi,
    pow = math.pow,
    rad = math.rad,
    random = math.random,
    sin = math.sin,
    sinh = math.sinh,
    sqrt = math.sqrt,
    tan = math.tan,
    tanh = math.tanh,
  },
  table = {
    concat = table.concat,
    insert = table.insert,
    maxn = table.maxn,
    remove = table.remove,
    sort = table.sort,
  },
  os = {
    clock = os.clock,
    difftime = os.difftime,
    time = os.time,
    datetable = safe_date,
  },
}
run code in environment (os.lua):

Code: Select all

-- [function] run code (in sandbox env)
function digicompute.os.run(f, env)
  setfenv(f, env)
  local e, msg = pcall(f)
  if e == false then return msg end
end

-- [function] run file under env
function digicompute.os.runfile(pos, path, errloc)
  local env = digicompute.create_env(pos)
  local f = loadfile(path) -- load func
  local e = digicompute.os.run(f, env) -- run func
  -- if error, call error handle function and re-run start
  if e then
    digicompute.os.handle_error(pos, e, errloc) -- handle error
    local s = loadfile(path.."/"..name.."/os/start.lua") -- load start func
    digicompute.os.run(s, env) -- run func
  end
end
callback from formspec on_receive_fields (api.lua):

Code: Select all

digicompute.os.runfile(pos, path.."/"..name.."/os/main.lua", "main")
As you might notice, all the functions defined should mostly be defined right inside the on_receive_fields, as they need access to the meta data. However, when I did that I was bombarded with even more errors about undefined global functions from the env table. I really don't think I'm doing this the best way, so that's why I'm asking.

In case you wonder, this is for my mod, digicompute. It aims to introduce fully functional Lua computers. If you'd like to see the full code, it can be accessed here after Wednesday, October 12th (I'm away from home, and something is up with my server). In the meantime, I've pushed the code to GitHub (and may leave it that way, IDK). There is now Wiki, and the README is from digiterm (the mod was originally a fork, but there's nothing from digiterm left - completely redesigned), but I'm working on a new one. Below is the error log.

error log:

Code: Select all

2016-10-09 17:53:27: WARNING[Server]: Undeclared global variable "set_output" accessed at ...inetest/worlds/digicompute/digicompute/oct8/os/start.lua:1
2016-10-09 17:53:27: WARNING[Server]: Undeclared global variable "get_string" accessed at ...inetest/worlds/digicompute/digicompute/oct8/os/start.lua:1
2016-10-09 17:53:27: ERROR[Main]: ServerError: Lua: Runtime error from mod 'digicompute' in callback node_on_receive_fields(): ...inetest/worlds/digicompute/digicompute/oct8/os/start.lua:1: attempt to call global 'get_string' (a nil value)
2016-10-09 17:53:27: ERROR[Main]: stack traceback:
2016-10-09 17:53:27: ERROR[Main]: 	...inetest/worlds/digicompute/digicompute/oct8/os/start.lua:1: in main chunk
2016-10-09 17:53:27: ERROR[Main]: 	[C]: in function 'dofile'
2016-10-09 17:53:27: ERROR[Main]: 	...inetest/worlds/digicompute/worldmods/digicompute/api.lua:308: in function <...inetest/worlds/digicompute/worldmods/digicompute/api.lua:295>
Thanks in advance.
Last edited by octacian on Sun Oct 16, 2016 15:05, edited 1 time in total.
MicroExpansion, Working Computers, All Projects - Check out my YouTube channel! (octacian)
I'm currently inactive in the Minetest community! So if I don't respond, that's why.

Byakuren
Member
Posts: 818
Joined: Tue Apr 14, 2015 01:59
GitHub: raymoo
IRC: Hijiri
In-game: Raymoo + Clownpiece

Re: Sandbox Env doesn't Recognize Functions

by Byakuren » Post

If your OS is supposed to be sandboxed, don't try to access the environment from it. If you want to provide some limited access to it, then you can put a function for doing that in the environment you pass (you will want to "localize" any global variables you use in the function):

Code: Select all

local function bad()
  -- This will access the global environment when it runs, and it won't be there
  global_thing.do_stuff()
end

local localized_thing = global_thing
local function good()
  -- This will access the variable declared above, which will point to the global thing from when the function was made
  localized_thing.do_stuff()
end
I'm more interested in how you are going to save the execution state between different computer runs. One way to do it might be to run the sandboxed code in a coroutine and insert yield() calls at the beginning of every loop and function body.
Every time a mod API is left undocumented, a koala dies.

User avatar
octacian
Member
Posts: 597
Joined: Mon Dec 21, 2015 22:18
GitHub: octacian
IRC: octacian
In-game: octacian
Location: Canada

Re: Sandbox Env doesn't Recognize Functions

by octacian » Post

Sorry, I guess I wasn't clear enough. os.lua simply defines several functions that were for use within the OS. I am moving them mostly to the formspec on_receive_fields though so that they have access to the position. The actual lua files run by the computer are stored in a physical file system on the server, the proper files being called depending on the actions (e.g. on_rightclick). I'm considering moving over to storing that data in meta entirely though. It might help to keep things more localized, and make things even safer (yes, sandbox should take care of safety, but I still gotta deal with people trying to cd out of the filesystem and such).

TL;DR, functions called from within the sandbox environment throw an error saying they are undefined. This happens even though the functions are defined before the environment.

I realize I didn't push the latest changes to the repo I linked, since they aren't complete (duh). So, here's the latest version.
MicroExpansion, Working Computers, All Projects - Check out my YouTube channel! (octacian)
I'm currently inactive in the Minetest community! So if I don't respond, that's why.

Byakuren
Member
Posts: 818
Joined: Tue Apr 14, 2015 01:59
GitHub: raymoo
IRC: Hijiri
In-game: Raymoo + Clownpiece

Re: Sandbox Env doesn't Recognize Functions

by Byakuren » Post

If the programs your computer runs calls these OS functions, the OS functions need to use things that are going to be in scope when you run the program. Global variables always look at the global environment, so it is going to try to get it from the program's environment. If you have "blah = memes" and "memes" is not a local variable, it tells Lua to go check the global environment to get its value. That means if the global environment changes, the location referred to by a global variable will change. So you need to localize the global function while you are still using the environment that contains the function you want (outside of your function). Either that, or put the functions you need in the environment you use when running your programs. If those functions use other globals, though, you will need to add all of those too.

That could easily be a mess, so another way you can do special calls is to use coroutines. Whenever your program needs to do a call requiring access to the minetest environment, you can instead yield with some arguments describing the kind of OS call you want to make, and handle the call outside of the continuation (and you would probably wrap this behavior up in a function). This makes your OS work a little bit more like a real operating system would.

Some pseudocode:

Code: Select all

-- The program we are running
local function program()
  local res = yield({ request_type = "meaning_of_life" })
  print("The answer to life, the universe, and everything is " .. res)
end

local process = coroutine.create(program)

-- Save the "real" global environment, and use our script one
local old_env = getenv()
setenv(special sandboxed environment)

-- Run our program until it makes a special call
local yielded, request = coroutine.resume(process)

-- Handle special calls
while yielded do

  -- Before we handle the call, switch in the "real" environment so we can do minetest things
  local process_env = getenv()
  setenv(old_env)
  local response
  if (request.request_type == "meaning_of_life") then
    -- Do Minetest stuff here, read node metadata, etc.
    response = 42
  end

  -- Restore the process's sandbox environment and give it the result of the call
  setenv(process_env)
  coroutine.resume(process, response)
end
I am not quite sure if this code would work, but it gives the overall flow, I think.

EDIT: Second paragraph
EDIT2: Pseudocode
Every time a mod API is left undocumented, a koala dies.

User avatar
octacian
Member
Posts: 597
Joined: Mon Dec 21, 2015 22:18
GitHub: octacian
IRC: octacian
In-game: octacian
Location: Canada

Re: Sandbox Env doesn't Recognize Functions

by octacian » Post

Solved. IRDK what I did. What I do know, is I just made a commit with way too many things in one. I think my issue though was, that the functions first didn't have the position of the computer, messing up the meta calls. It also seemed as they needed to be more closely related with the env table. In the end, I created a function, digicompute.create_env(pos, fields), obviously accepting position for meta, and fields for the form fields (I/O).

EDIT: I'm def gonna try and add a chapter on environments to rubenwardy's modding book...
MicroExpansion, Working Computers, All Projects - Check out my YouTube channel! (octacian)
I'm currently inactive in the Minetest community! So if I don't respond, that's why.

Post Reply

Who is online

Users browsing this forum: No registered users and 11 guests