Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
pllua-ng/src/trusted.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
938 lines (839 sloc)
21.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* trusted.c */ | |
| #include "pllua.h" | |
| /* from init.c */ | |
| extern bool pllua_do_install_globals; | |
| /* | |
| * Trusted versions or wrappers for functionality that we need to restrict in a | |
| * trusted interpreter. | |
| * | |
| */ | |
| /* | |
| * this defines the trusted subset of the "os" package (installed as | |
| * "trusted.os" in the outer environment) | |
| */ | |
| static struct luaL_Reg trusted_os_funcs[] = { | |
| { "date", NULL }, | |
| { "clock", NULL }, | |
| { "time", NULL }, | |
| { "difftime", NULL }, | |
| { NULL, NULL } | |
| }; | |
| static int | |
| pllua_open_trusted_os(lua_State *L) | |
| { | |
| const luaL_Reg *p; | |
| lua_getglobal(L, "os"); | |
| luaL_newlibtable(L, trusted_os_funcs); | |
| for (p = trusted_os_funcs; p->name; ++p) | |
| { | |
| lua_getfield(L, -2, p->name); | |
| lua_setfield(L, -2, p->name); | |
| } | |
| return 1; | |
| } | |
| /* | |
| * load(chunk[,chunkname[,mode[,env]]]) | |
| * | |
| * Wrapper must force "mode" to be "t" to disallow loading binary chunks. Also | |
| * must force "env" to be the sandbox env if not supplied by the caller. | |
| * | |
| * Punts to _G.load after munging the args. | |
| */ | |
| static int | |
| pllua_t_load(lua_State *L) | |
| { | |
| int nargs = lua_gettop(L); | |
| if (nargs < 4) | |
| { | |
| lua_settop(L, 3); | |
| nargs = 4; | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); | |
| } | |
| lua_pushstring(L, "t"); | |
| lua_replace(L, 3); | |
| lua_getglobal(L, "load"); | |
| lua_insert(L, 1); | |
| lua_call(L, nargs, LUA_MULTRET); | |
| return lua_gettop(L); | |
| } | |
| /* | |
| * user-facing "require" function | |
| */ | |
| static void pllua_t_require_findloader(lua_State *L, int nd, const char *name); | |
| static int | |
| pllua_t_require(lua_State *L) | |
| { | |
| const char *name = luaL_checkstring(L, 1); | |
| lua_settop(L, 1); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); | |
| lua_getfield(L, 2, name); /* LOADED[name] */ | |
| if (lua_toboolean(L, -1)) /* is it there? */ | |
| return 1; /* package is already loaded */ | |
| lua_pop(L, 1); /* remove 'getfield' result */ | |
| if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) | |
| luaL_error(L, "'package.searchers' must be a table"); | |
| pllua_t_require_findloader(L, -1, name); | |
| lua_pushstring(L, name); /* pass name as argument to module loader */ | |
| lua_insert(L, -2); /* name is 1st argument (before search data) */ | |
| lua_call(L, 2, 1); /* run loader to load module */ | |
| /* | |
| * If the module returned nil, see if it stored a non-nil value in the | |
| * loaded table itself (older deprecated protocol). If not, use "true". | |
| */ | |
| if (lua_isnil(L, -1) && | |
| lua_getfield(L, 2, name) == LUA_TNIL) | |
| lua_pushboolean(L, 1); /* use true as result */ | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, 2, name); /* LOADED[name] = returned value */ | |
| return 1; | |
| } | |
| /* | |
| * "require" function helper | |
| */ | |
| static void | |
| pllua_t_require_findloader(lua_State *L, int nd, const char *name) | |
| { | |
| int i; | |
| luaL_Buffer msg; /* to build error message */ | |
| nd = lua_absindex(L, nd); | |
| luaL_buffinit(L, &msg); | |
| /* iterate over available searchers to find a loader */ | |
| for (i = 1; ; ++i) | |
| { | |
| if (lua_rawgeti(L, nd, i) == LUA_TNIL) | |
| { | |
| lua_pop(L, 1); /* remove nil */ | |
| luaL_pushresult(&msg); /* create error message */ | |
| luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); | |
| } | |
| lua_pushstring(L, name); | |
| lua_call(L, 1, 2); /* call it */ | |
| if (lua_isfunction(L, -2)) /* did it find a loader? */ | |
| return; /* module loader found */ | |
| else if (lua_isstring(L, -2)) /* searcher returned error message? */ | |
| { | |
| lua_pop(L, 1); /* remove extra return */ | |
| luaL_addvalue(&msg); /* concatenate error message */ | |
| } | |
| else | |
| lua_pop(L, 2); /* remove both returns */ | |
| } | |
| } | |
| /* | |
| * searcher functions are called as | |
| * | |
| * searcher(name) returns func,arg | |
| * | |
| */ | |
| static int | |
| pllua_package_preload_search(lua_State *L) | |
| { | |
| /* preload searcher works entirely inside the sandbox */ | |
| const char *name = luaL_checkstring(L, 1); | |
| lua_getfield(L, lua_upvalueindex(1), "preload"); | |
| lua_pushstring(L, name); | |
| if (lua_gettable(L, -2) == LUA_TNIL) | |
| { | |
| lua_pushfstring(L, "\n\tno field package.preload['%s']", name); | |
| return 1; | |
| } | |
| lua_pushnil(L); | |
| return 2; | |
| } | |
| static int | |
| pllua_package_allowed_search(lua_State *L) | |
| { | |
| /* | |
| * allowed searcher works outside the sandbox; the sandbox can't see its | |
| * own "allow" list | |
| */ | |
| const char *name = luaL_checkstring(L, 1); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); | |
| lua_pushstring(L, name); | |
| if (lua_gettable(L, -2) == LUA_TNIL) | |
| { | |
| lua_pushfstring(L, "\n\tno module '%s' in list of allowed modules", name); | |
| return 1; | |
| } | |
| lua_pushnil(L); | |
| return 2; | |
| } | |
| static int | |
| pllua_open_trusted_package(lua_State *L) | |
| { | |
| lua_newtable(L); | |
| lua_pushvalue(L, -1); | |
| lua_pushcclosure(L, pllua_t_require, 1); | |
| lua_setfield(L, -2, "require"); | |
| lua_newtable(L); | |
| lua_pushvalue(L, -1); | |
| lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); | |
| lua_setfield(L, -2, "loaded"); | |
| lua_newtable(L); | |
| lua_setfield(L, -2, "preload"); | |
| lua_newtable(L); | |
| /* first entry in searchers list is the preload searcher */ | |
| lua_pushvalue(L, -2); | |
| lua_pushcclosure(L, pllua_package_preload_search, 1); | |
| lua_seti(L, -2, 1); | |
| /* second entry in searchers list is the permitted-package searcher */ | |
| /* this operates outside the sandbox so we don't close it over sandbox.package */ | |
| lua_pushcfunction(L, pllua_package_allowed_search); | |
| lua_seti(L, -2, 2); | |
| lua_setfield(L, -2, "searchers"); | |
| return 1; | |
| } | |
| /* | |
| * These funcs appear as trusted.func outside the sandbox, for management | |
| * purposes. | |
| * | |
| * trusted.require("module", ["newname"], "mode") | |
| * -- as if _G.newname = module was done inside the sandbox (the | |
| * actual 'require "module"' is done outside) | |
| * | |
| * trusted.allow("module", ["newname"], "mode", "globname") | |
| * -- allow require "newname" to work inside the sandbox | |
| * note that "module" WILL be loaded immediately (outside) | |
| * | |
| * trusted.remove("newname","globname") | |
| * -- remove the module from the sandbox; INEFFECTIVE if code has already | |
| * been run inside. | |
| * | |
| * modules "require"d outside of the sandbox are not exposed as global | |
| * variables inside it unless specified with require or allow. However, | |
| * anything a module stores inside itself, including references to other | |
| * modules, will be accessible if the module is. | |
| * | |
| * CAVEAT SUPERUSER: it will be very hard to ensure that any given loaded | |
| * module doesn't expose the real global table, its functions, or dangerous | |
| * packages to the untrusted code. | |
| */ | |
| static int | |
| pllua_bind_one_value(lua_State *L) | |
| { | |
| lua_pushvalue(L, lua_upvalueindex(1)); | |
| return 1; | |
| } | |
| static int | |
| pllua_bind_one_call(lua_State *L) | |
| { | |
| int i; | |
| lua_settop(L, 0); | |
| for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); ++i) | |
| { | |
| if (i >= 10 && (i % 10) == 0) | |
| luaL_checkstack(L, 20, NULL); | |
| lua_pushvalue(L, lua_upvalueindex(i)); | |
| } | |
| if (i < 2) | |
| return 0; | |
| lua_call(L, i-2, LUA_MULTRET); | |
| return lua_gettop(L); | |
| } | |
| /* | |
| * f(modefunc,requirefunc,modulename) | |
| * = return modefunc(requirefunc(modulename)) | |
| * | |
| * This does the actual out-of-sandbox require, it's split into its own | |
| * function so that we can wrap it up as a closure for deferred execution | |
| */ | |
| static int | |
| pllua_do_trusted_require(lua_State *L) | |
| { | |
| lua_settop(L, 3); | |
| lua_call(L, 1, 1); | |
| lua_call(L, 1, 1); | |
| return 1; | |
| } | |
| /* | |
| * _allow(modname,newname,mode,global,load_now) | |
| */ | |
| static int | |
| pllua_trusted_allow(lua_State *L) | |
| { | |
| bool load_now = false; | |
| lua_settop(L, 5); | |
| luaL_checkstring(L, 1); | |
| luaL_optstring(L, 2, NULL); | |
| if (lua_isnil(L, 2)) | |
| { | |
| lua_pushvalue(L, 1); | |
| lua_replace(L, 2); | |
| } | |
| if (lua_type(L, 4) == LUA_TBOOLEAN) | |
| { | |
| if (lua_toboolean(L, 4)) | |
| lua_pushvalue(L, 2); | |
| else | |
| lua_pushnil(L); | |
| lua_replace(L, 4); | |
| } | |
| else | |
| luaL_optstring(L, 4, NULL); | |
| if (!lua_isnil(L, 4) || lua_toboolean(L, 5)) | |
| load_now = true; | |
| if (!lua_isfunction(L, 3)) | |
| { | |
| const char *mode = luaL_optstring(L, 3, "proxy"); | |
| lua_getfield(L, lua_upvalueindex(2), mode); | |
| if (!lua_isfunction(L, -1)) | |
| luaL_error(L, "trusted.modes value is not a function"); | |
| lua_replace(L, 3); | |
| } | |
| lua_pushcfunction(L, pllua_do_trusted_require); | |
| lua_pushvalue(L, 3); | |
| lua_pushvalue(L, lua_upvalueindex(3)); /* _G.require */ | |
| lua_pushvalue(L, 1); | |
| if (load_now) | |
| { | |
| lua_call(L, 3, 1); | |
| lua_pushvalue(L, -1); | |
| lua_pushcclosure(L, pllua_bind_one_value, 1); | |
| } | |
| else | |
| lua_pushcclosure(L, pllua_bind_one_call, 4); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); | |
| lua_pushvalue(L, 2); | |
| lua_pushvalue(L, -3); | |
| lua_rawset(L, -3); | |
| lua_pop(L, 1); | |
| if (lua_isnil(L, 4)) | |
| return 0; | |
| lua_pop(L, 1); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); | |
| lua_pushvalue(L, 2); | |
| lua_pushvalue(L, -3); | |
| lua_rawset(L, -3); | |
| lua_pop(L, 1); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); | |
| lua_pushvalue(L, 4); | |
| lua_pushvalue(L, -3); | |
| lua_rawset(L, -3); | |
| lua_pop(L, 1); | |
| return 0; | |
| } | |
| static int | |
| pllua_trusted_remove(lua_State *L) | |
| { | |
| lua_settop(L, 2); | |
| luaL_checkstring(L, 1); | |
| if (lua_type(L, 2) == LUA_TBOOLEAN) | |
| { | |
| if (lua_toboolean(L, 2)) | |
| lua_pushvalue(L, 1); | |
| else | |
| lua_pushnil(L); | |
| lua_replace(L, 2); | |
| } | |
| else | |
| luaL_optstring(L, 2, NULL); | |
| /* kill sandbox's _G.globname */ | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); | |
| lua_pushvalue(L, 2); | |
| lua_pushnil(L); | |
| lua_rawset(L, -3); | |
| /* kill ALLOW and LOADED entries for modname */ | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); | |
| lua_pushvalue(L, 1); | |
| lua_pushnil(L); | |
| lua_rawset(L, -3); | |
| lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); | |
| lua_pushvalue(L, 1); | |
| lua_pushnil(L); | |
| lua_rawset(L, -3); | |
| return 0; | |
| } | |
| /* | |
| * upvalue 1 is our own closure, upvalue 2 is the memo table | |
| */ | |
| static int | |
| pllua_trusted_mode_copy_inner(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| lua_pushvalue(L, 1); | |
| if (lua_rawget(L, lua_upvalueindex(2)) != LUA_TNIL) | |
| return 1; | |
| lua_pop(L, 1); | |
| lua_newtable(L); /* slot 2 */ | |
| lua_pushvalue(L, 1); | |
| lua_pushvalue(L, 2); | |
| lua_rawset(L, lua_upvalueindex(2)); | |
| /* | |
| * We intentionally raw-iterate rather than pairs()ing | |
| */ | |
| lua_pushnil(L); | |
| while (lua_next(L, 1)) | |
| { | |
| /* ... key val */ | |
| lua_pushvalue(L, -2); | |
| lua_insert(L, -2); | |
| /* ... key key val */ | |
| if (lua_type(L, -1) == LUA_TTABLE) | |
| { | |
| lua_pushvalue(L, lua_upvalueindex(1)); | |
| lua_insert(L, -2); | |
| lua_call(L, 1, 1); | |
| } | |
| lua_rawset(L, 2); | |
| /* ... key */ | |
| } | |
| return 1; | |
| } | |
| static int | |
| pllua_trusted_mode_scopy(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| lua_newtable(L); /* slot 2 */ | |
| /* | |
| * We intentionally raw-iterate rather than pairs()ing | |
| */ | |
| lua_pushnil(L); | |
| while (lua_next(L, 1)) | |
| { | |
| /* ... key val */ | |
| lua_pushvalue(L, -2); | |
| lua_insert(L, -2); | |
| /* ... key key val */ | |
| lua_rawset(L, 2); | |
| /* ... key */ | |
| } | |
| return 1; | |
| } | |
| static int | |
| pllua_trusted_mode_direct(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| return 1; | |
| } | |
| /* | |
| * Proxy a function call. | |
| * | |
| * Upvalue 1 is the real function to call. | |
| * Upvalue 2 is the value to sub for the first arg (self). | |
| */ | |
| static int | |
| pllua_trusted_mode_proxy_wrap(lua_State *L) | |
| { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| if (lua_gettop(L) > 1) | |
| lua_replace(L, 1); | |
| lua_pushvalue(L, lua_upvalueindex(1)); | |
| lua_insert(L, 1); | |
| lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); | |
| return lua_gettop(L); | |
| } | |
| /* | |
| * Common code for metatable handling between "proxy" and "sproxy" modes | |
| */ | |
| static void | |
| pllua_trusted_mode_proxy_metatable(lua_State *L, int ot, int mt) | |
| { | |
| /* | |
| * Logic for metatables: | |
| * | |
| * __index: | |
| * always points to the old table, whether or not the old | |
| * metatable has it | |
| * __newindex: | |
| * Points to the old table iff the old metatable has a | |
| * __newindex entry, otherwise is not set | |
| * __call: | |
| * wrapped as a function call if present | |
| * __metatable: | |
| * copied if present, otherwise set to true | |
| * any other key: | |
| * just copied, since we can't hope to guess the semantics | |
| * | |
| */ | |
| if (lua_getmetatable(L, ot)) | |
| { | |
| lua_pushnil(L); | |
| while (lua_next(L, -2)) | |
| { | |
| const char *keyname = lua_tostring(L, -2); | |
| /* metatab key val */ | |
| if (strcmp(keyname, "__index") == 0) | |
| lua_pop(L, 1); | |
| else if (strcmp(keyname, "__newindex") == 0) | |
| { | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, mt, keyname); | |
| lua_pop(L, 1); | |
| } | |
| else if (strcmp(keyname, "__call") == 0) | |
| { | |
| lua_pushvalue(L, 1); | |
| lua_pushcclosure(L, pllua_trusted_mode_proxy_wrap, 2); | |
| lua_setfield(L, mt, keyname); | |
| } | |
| else | |
| { | |
| lua_pushvalue(L, -2); | |
| lua_insert(L, -2); | |
| /* ... key key val */ | |
| lua_rawset(L, mt); | |
| } | |
| /* metatab key */ | |
| } | |
| lua_pop(L, 1); | |
| } | |
| } | |
| static int | |
| pllua_trusted_mode_sproxy(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| if (lua_type(L, 1) != LUA_TTABLE) | |
| return 1; | |
| lua_newtable(L); /* slot 2 */ | |
| lua_newtable(L); /* slot 3 for now */ | |
| lua_pushboolean(L, 1); | |
| lua_setfield(L, -2, "__metatable"); | |
| pllua_trusted_mode_proxy_metatable(L, 1, 3); | |
| lua_pushvalue(L, 1); | |
| lua_setfield(L, -2, "__index"); | |
| lua_setmetatable(L, 2); | |
| return 1; | |
| } | |
| static int | |
| pllua_trusted_mode_proxy_inner(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| if (lua_type(L, 1) != LUA_TTABLE) | |
| return 1; | |
| lua_pushvalue(L, 1); | |
| if (lua_rawget(L, lua_upvalueindex(2)) != LUA_TNIL) | |
| return 1; | |
| lua_pop(L, 1); | |
| lua_newtable(L); /* slot 2 */ | |
| lua_pushvalue(L, 1); | |
| lua_pushvalue(L, 2); | |
| lua_rawset(L, lua_upvalueindex(2)); | |
| lua_newtable(L); /* slot 3 for now */ | |
| lua_pushboolean(L, 1); | |
| lua_setfield(L, -2, "__metatable"); | |
| pllua_trusted_mode_proxy_metatable(L, 1, 3); | |
| lua_pushvalue(L, 1); | |
| lua_setfield(L, -2, "__index"); | |
| lua_setmetatable(L, 2); | |
| /* | |
| * We intentionally raw-iterate rather than pairs()ing | |
| */ | |
| lua_pushnil(L); | |
| while (lua_next(L, 1)) | |
| { | |
| /* ... key val */ | |
| if (lua_type(L, -1) == LUA_TTABLE) | |
| { | |
| lua_pushvalue(L, -2); | |
| lua_insert(L, -2); | |
| /* ... key key val */ | |
| lua_pushvalue(L, lua_upvalueindex(1)); | |
| lua_insert(L, -2); | |
| lua_call(L, 1, 1); | |
| lua_rawset(L, 2); | |
| } | |
| else | |
| lua_pop(L, 1); | |
| /* ... key */ | |
| } | |
| return 1; | |
| } | |
| static int | |
| pllua_trusted_mode_outer(lua_State *L) | |
| { | |
| lua_settop(L, 1); | |
| if (lua_type(L, 1) != LUA_TTABLE) | |
| return 1; | |
| lua_pushnil(L); | |
| lua_newtable(L); | |
| if (lua_toboolean(L, lua_upvalueindex(1))) | |
| lua_pushcclosure(L, pllua_trusted_mode_proxy_inner, 2); | |
| else | |
| lua_pushcclosure(L, pllua_trusted_mode_copy_inner, 2); | |
| lua_pushvalue(L, -1); | |
| lua_setupvalue(L, -2, 1); | |
| lua_insert(L, 1); | |
| lua_call(L, 1, 1); | |
| return 1; | |
| } | |
| static struct luaL_Reg trusted_modes_funcs[] = { | |
| { "direct", pllua_trusted_mode_direct }, | |
| { "scopy", pllua_trusted_mode_scopy }, | |
| { "sproxy", pllua_trusted_mode_sproxy }, | |
| { NULL, NULL } | |
| }; | |
| static struct luaL_Reg trusted_funcs[] = { | |
| { "_allow", pllua_trusted_allow }, | |
| { "remove", pllua_trusted_remove }, | |
| { NULL, NULL } | |
| }; | |
| /* | |
| * This is called with the first arg being the "trusted" module table | |
| */ | |
| static const char *trusted_lua = | |
| "local lib = ...\n" | |
| "local unpack = table.unpack or unpack\n" | |
| "local type, ipairs = type, ipairs\n" | |
| "local allow = lib._allow\n" | |
| #if LUA_VERSION_NUM >= 502 | |
| "_ENV = nil\n" | |
| #endif | |
| "function lib.allow(mod,new,mode,glob,immed)\n" | |
| " if type(mod)==\"string\" then\n" | |
| " allow(mod,new,mode,glob,immed)\n" | |
| " elseif type(mod)==\"table\" then\n" | |
| " for i,v in ipairs(mod) do\n" | |
| " local e_mod, e_new, e_mode, e_glob, e_immed\n" | |
| " = unpack(type(v)==\"table\" and v or { v },1,5)\n" | |
| " if e_glob == nil then e_glob = glob end\n" | |
| " if e_immed == nil then e_immed = immed end\n" | |
| " allow(e_mod, e_new, e_mode or mode, e_glob, e_immed)\n" | |
| " end\n" | |
| " end\n" | |
| "end\n" | |
| "function lib.require(mod,new,mode)\n" | |
| " lib.allow(mod,new,mode,true)\n" | |
| "end\n" | |
| ; | |
| static struct luaL_Reg sandbox_funcs[] = { | |
| /* from this file */ | |
| { "load", pllua_t_load }, | |
| /* "require" is set from package.require */ | |
| {NULL, NULL} | |
| }; | |
| /* | |
| * Whitelist the standard lua globals that we allow into the sandbox. | |
| */ | |
| struct global_info { | |
| const char *name; | |
| const char *libname; | |
| }; | |
| static struct global_info sandbox_lua_globals[] = { | |
| /* base lib */ | |
| { "assert", NULL }, | |
| { "collectgarbage", NULL }, | |
| { "error", NULL }, | |
| { "getmetatable", NULL }, | |
| { "ipairs", NULL }, | |
| { "next", NULL }, | |
| { "pairs", NULL }, | |
| { "rawequal", NULL }, | |
| { "rawlen", NULL }, | |
| { "rawget", NULL }, | |
| { "rawset", NULL }, | |
| { "select", NULL }, | |
| { "setmetatable", NULL }, | |
| { "tonumber", NULL }, | |
| { "tostring", NULL }, | |
| { "type", NULL }, | |
| { "unpack", NULL }, /* for luajit */ | |
| { "warn", NULL }, | |
| { "_VERSION", NULL }, | |
| { "_PLVERSION", NULL }, | |
| { "_PLREVISION", NULL }, | |
| { "_PL_LOAD_TIME", NULL }, | |
| { "_PL_IDENT", NULL }, | |
| { "_PG_VERSION", NULL }, | |
| { "_PG_VERSION_NUM", NULL }, | |
| { NULL, "pllua.print" }, | |
| { "print", NULL }, | |
| { NULL, "pllua.error" }, | |
| { "pcall", NULL }, | |
| { "xpcall", NULL }, | |
| { "lpcall", NULL }, | |
| { NULL, "pllua.trusted.package" }, | |
| { "require", NULL }, | |
| { NULL, NULL } | |
| }; | |
| /* | |
| * List of packages to expose to the sandbox by default | |
| * | |
| * "mode" should be either "copy" or "proxy" for anything that might get used | |
| * by unsandboxed code. "direct" is ok for the trusted OS library because that | |
| * is not used outside the sandbox. | |
| */ | |
| struct module_info | |
| { | |
| const char *name; | |
| const char *newname; | |
| const char *mode; | |
| const char *globname; | |
| }; | |
| static struct module_info sandbox_packages_early[] = { | |
| { "coroutine", NULL, "copy", "coroutine" }, | |
| { "string", NULL, "copy", "string" }, | |
| #if LUA_VERSION_NUM >= 503 | |
| { "utf8", NULL, "copy", "utf8" }, | |
| #endif | |
| { "table", NULL, "copy", "table" }, | |
| { "math", NULL, "copy", "math" }, | |
| { "pllua.trusted.os", "os", "direct", "os" }, | |
| { "pllua.trusted.package", "package", "direct", "package" }, | |
| { "pllua.error", NULL, "copy", NULL }, | |
| { NULL, NULL, NULL, NULL } | |
| }; | |
| static struct module_info sandbox_packages_late[] = { | |
| { "pllua.spi", NULL, "proxy", "spi" }, | |
| { "pllua.pgtype", NULL, "proxy", "pgtype" }, | |
| { "pllua.elog", NULL, "copy", NULL }, | |
| { "pllua.numeric", NULL, "copy", NULL }, | |
| { "pllua.jsonb", NULL, "copy", NULL }, | |
| { "pllua.time", NULL, "copy", NULL }, | |
| { NULL, NULL, NULL, NULL } | |
| }; | |
| /* | |
| * This isn't really a module but handles the late initialization phase. | |
| */ | |
| int | |
| pllua_open_trusted_late(lua_State *L) | |
| { | |
| const struct module_info *np; | |
| lua_settop(L, 0); | |
| luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); | |
| lua_getfield(L, -1, "pllua.trusted"); | |
| lua_replace(L, 1); | |
| for (np = sandbox_packages_late; np->name; ++np) | |
| { | |
| lua_getfield(L, 1, "_allow"); | |
| lua_pushstring(L, np->name); | |
| if (np->newname) | |
| lua_pushstring(L, np->newname); | |
| else | |
| lua_pushnil(L); | |
| lua_pushstring(L, np->mode); | |
| if (np->globname && pllua_do_install_globals) | |
| lua_pushstring(L, np->globname); | |
| else | |
| lua_pushnil(L); | |
| lua_pushboolean(L, 1); | |
| lua_call(L, 5, 0); | |
| } | |
| lua_pushvalue(L, 1); | |
| return 1; | |
| } | |
| int | |
| pllua_open_trusted(lua_State *L) | |
| { | |
| const struct global_info *p; | |
| const struct module_info *np; | |
| lua_settop(L,0); | |
| /* create the package table itself: index 1 */ | |
| luaL_newlibtable(L, trusted_funcs); | |
| lua_pushvalue(L, 1); | |
| lua_newtable(L); | |
| luaL_setfuncs(L, trusted_modes_funcs, 0); | |
| lua_pushboolean(L, 0); | |
| lua_pushcclosure(L, pllua_trusted_mode_outer, 1); | |
| lua_setfield(L, -2, "copy"); | |
| lua_pushboolean(L, 1); | |
| lua_pushcclosure(L, pllua_trusted_mode_outer, 1); | |
| lua_setfield(L, -2, "proxy"); | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, 1, "modes"); | |
| lua_getglobal(L, "require"); | |
| luaL_setfuncs(L, trusted_funcs, 3); | |
| if (luaL_loadbuffer(L, trusted_lua, strlen(trusted_lua), "trusted.lua") == LUA_OK) | |
| { | |
| lua_pushvalue(L, 1); | |
| lua_call(L, 1, 0); | |
| } | |
| else | |
| lua_error(L); | |
| /* create the "permitted package" table */ | |
| lua_newtable(L); | |
| lua_pushvalue(L, -1); | |
| lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); | |
| lua_setfield(L, 1, "permit"); | |
| /* create the infrastructure of the sandbox module system */ | |
| luaL_requiref(L, "pllua.trusted.package", pllua_open_trusted_package, 0); | |
| lua_pop(L, 1); | |
| /* create the trusted sandbox: index 2 */ | |
| lua_newtable(L); | |
| lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); | |
| lua_pushglobaltable(L); | |
| for (p = sandbox_lua_globals; p->name || p->libname; ++p) | |
| { | |
| if (p->libname) | |
| { | |
| lua_getfield(L, -2, p->libname); | |
| lua_replace(L, -2); | |
| } | |
| if (p->name) | |
| { | |
| lua_getfield(L, -1, p->name); | |
| lua_setfield(L, 2, p->name); | |
| } | |
| } | |
| lua_pop(L, 2); | |
| lua_pushvalue(L, 2); | |
| lua_setfield(L, 2, "_G"); | |
| luaL_setfuncs(L, sandbox_funcs, 0); | |
| lua_pushvalue(L, 2); | |
| lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); | |
| lua_pushvalue(L, 2); | |
| lua_setfield(L, 1, "sandbox"); | |
| /* proxy metatable for the sandbox */ | |
| lua_newtable(L); | |
| lua_pushvalue(L, 2); | |
| lua_setfield(L, -2, "__index"); | |
| lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_SANDBOX_META); | |
| /* create the minimal trusted "os" library */ | |
| luaL_requiref(L, "pllua.trusted.os", pllua_open_trusted_os, 0); | |
| lua_pop(L, 1); | |
| /* | |
| * require standard modules into the sandbox | |
| */ | |
| lua_getfield(L, 1, "_allow"); | |
| for (np = sandbox_packages_early; np->name; ++np) | |
| { | |
| lua_pushvalue(L, -1); | |
| lua_pushstring(L, np->name); | |
| if (np->newname) | |
| lua_pushstring(L, np->newname); | |
| else | |
| lua_pushnil(L); | |
| lua_pushstring(L, np->mode); | |
| if (np->globname) | |
| lua_pushstring(L, np->globname); | |
| else | |
| lua_pushnil(L); | |
| lua_pushboolean(L, 1); | |
| lua_call(L, 5, 0); | |
| } | |
| lua_pop(L, 1); | |
| /* | |
| * Ugly hack; we can't tell reliably at compile time whether the lua | |
| * library we're linked to enables bit32 or not. So just check whether | |
| * it exists and if so, run _allow for it as a special case. | |
| */ | |
| #ifdef LUA_BITLIBNAME | |
| lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); | |
| lua_getfield(L, -1, LUA_BITLIBNAME); | |
| if (!lua_isnil(L, -1)) | |
| { | |
| lua_getfield(L, 1, "_allow"); | |
| lua_pushstring(L, LUA_BITLIBNAME); | |
| lua_pushnil(L); | |
| lua_pushstring(L, "copy"); | |
| lua_pushboolean(L, 1); | |
| lua_call(L, 4, 0); | |
| } | |
| lua_pop(L, 2); | |
| #endif | |
| /* | |
| * global "string" is the metatable for all string objects. We don't | |
| * want the sandbox to be able to get it via getmetatable("") | |
| */ | |
| lua_pushstring(L, ""); | |
| if (lua_getmetatable(L, -1)) | |
| { | |
| lua_pushboolean(L, 1); | |
| lua_setfield(L, -2, "__metatable"); | |
| lua_pop(L, 2); | |
| } | |
| else | |
| lua_pop(L, 1); | |
| /* done */ | |
| lua_pushvalue(L, 1); | |
| return 1; | |
| } |