minetest-mod-security/sandboxLuaStdlib-5.1.lua
2015-06-01 15:22:11 -07:00

621 lines
18 KiB
Lua

local security = select(1, ...);
local std = security.std;
local sandboxEnv = security.sandboxEnv;
-- Utility Functions
local stdError = std.error;
local stdSetFEnv = std.setfenv;
local stdToString = std.tostring;
local stdType = std.type;
local stdStrFind = std.string.find;
local stdTabConcat = std.table.concat;
local stdTabInsert = std.table.insert;
local stdTabRemove = std.table.remove;
local split;
do
local stdStrSub = std.string.sub;
split = function(str, sep, plain)
local comps = {};
if #str <= 0 then return comps; end;
local p = 1;
while true do
local s, e = stdStrFind(str, sep, p, plain);
if s then
stdTabInsert(comps, stdStrSub(str, p, s - 1));
p = e + 1;
else
stdTabInsert(comps, stdStrSub(str, p));
break;
end;
end;
return comps;
end;
end;
-- Top-Level Whitelist
local TOP_LEVEL_WHITELIST =
{
_G = false, -- Replaced by environment itself
_VERSION = true,
assert = true,
bit = true, -- (luajit)
collectgarbage = true,
dofile = false, -- Wrapped below
error = true,
gcinfo = true, -- Deprecated, but okay
getfenv = false, -- Wrapped below
getmetatable = false, -- Wrapped below
ipairs = true,
jit = false, -- Removed completely (luajit)
load = false, -- Wrapped below
loadfile = false, -- Wrapped below
loadstring = false, -- Wrapped below
module = false, -- Wrapped below
newproxy = false, -- Short-lived, deprecated; probably unused.
next = true,
pairs = true,
pcall = true,
print = true,
rawequal = true,
rawget = true,
rawset = true,
require = false, -- Wrapped below
select = true,
setfenv = false, -- Wrapped below
setmetatable = false, -- Wrapped below
tonumber = true,
tostring = true,
type = true,
unpack = true,
xpcall = true,
};
for symbol, whitelisted in pairs(TOP_LEVEL_WHITELIST) do
if whitelisted then
sandboxEnv[symbol] = std[symbol];
end;
end;
-- Standard Package Whitelist
sandboxEnv.coroutine = std.coroutine;
sandboxEnv.debug = nil; -- Removed completely
sandboxEnv.io = {}; -- Wrapped below
sandboxEnv.math = std.math;
sandboxEnv.os = {}; -- Wrapped below
sandboxEnv.package = {}; -- Wrapped below
sandboxEnv.string = std.string;
sandboxEnv.table = std.table;
-- Virtual Filesystem
local toRealPath;
local wrappedOsTmpName;
do
local MOD_NAME = security.MOD_NAME;
local WORLD_PATH = security.WORLD_PATH;
local mtGetModPath = security.mtGetModPath;
local stdOsTmpName = std.os.tmpname;
local stdStrGsub = std.string.gsub;
local tmpPathsCounter = 1;
local tmpPaths = {};
local function sanitizeAndSplitFilePath(path)
-- Escape characters and Windows path separators
if stdStrFind(path, "%", 1, true) then return nil; end;
path = stdStrGsub(path, "\\", "/");
local segs = split(path, "/", true);
local filteredSegs = {};
for i, seg in ipairs(segs) do
if seg and #seg > 0 and seg ~= "." then
stdTabInsert(filteredSegs, seg);
elseif i >= #segs then
return nil;
elseif seg == ".." then
if #filteredSegs > 0 then
stdTabRemove(filteredSegs);
else
return nil;
end;
end;
end;
return (#filteredSegs > 0 and filteredSegs) or nil;
end;
toRealPath = function(path)
local tmpPath = tmpPaths[path];
if tmpPath then return tmpPath; end;
if path and
(stdStrFind(path, "^%.[/\\]") or not stdStrFind(path, "^[/\\]"))
then
path = "/world/" .. path;
end;
local segs = sanitizeAndSplitFilePath(path);
if segs and #segs >= 2 then
if segs[1] == "world" then
return WORLD_PATH .. "/" .. stdTabConcat(segs, "/", 2);
elseif segs[1] == "mods" and #segs >= 3 then
local modName = segs[2];
if modName ~= MOD_NAME then
local modPath = mtGetModPath(modName);
if modPath and #modPath > 0 then
return modPath .. "/" .. stdTabConcat(segs, "/", 3);
end;
end;
end;
end;
return nil, "Bad virtual filesystem location '"..path.."'";
end;
wrappedOsTmpName = function()
local p = "/tmp/tmp" .. tmpPathsCounter;
tmpPathsCounter = tmpPathsCounter + 1;
tmpPaths[p] = stdOsTmpName();
return p;
end;
end;
local wrappedIoOpen;
do
local stdIoOpen = std.io.open;
wrappedIoOpen = function(filePath, mode)
local realPath, err = toRealPath(filePath);
if not realPath then return nil, err; end;
local file = stdIoOpen(realPath, mode);
if file then
return file;
else
return nil, "Error opening file '"..filePath.."'";
end;
end;
end;
local wrappedIoInput;
local wrappedIoLines;
local wrappedIoOutput;
local wrappedIoRead;
local wrappedIoWrite;
local wrappedOsRemove;
local wrappedOsRename;
do
local stdOsRemove = std.os.remove;
local stdOsRename = std.os.rename;
local stdIoInput = std.io.input;
local stdIoLines = std.io.lines;
local stdIoOutput = std.io.output;
local stdIoType = std.io.type;
wrappedIoInput = function(file)
if file == nil then
return stdIoInput();
elseif stdIoType(file) then
return stdIoInput(file);
else
local fileName = stdToString(file);
if not fileName then
return stdError("Not a file handle or file name string");
end;
local realPath, err = toRealPath(fileName);
if not realPath then return stdError(err); end;
return stdIoInput(realPath);
end;
end;
wrappedIoLines = function(fileName)
if fileName == nil then
return stdIoInput():lines();
end;
fileName = stdToString(fileName);
if not fileName then return stdError("Not a file name string"); end;
local realPath, err = toRealPath(fileName);
if not realPath then return stdError(err); end;
return stdIoLines(realPath);
end;
wrappedIoOutput = function(file)
if file == nil then
return stdIoOutput();
elseif stdIoType(file) then
return stdIoOutput(file);
else
local fileName = stdToString(file);
if not fileName then
return stdError("Not a file handle or file name string");
end;
local realPath, err = toRealPath(fileName);
if not realPath then return stdError(err); end;
return stdIoOutput(realPath);
end;
end;
wrappedIoRead = function(...)
return wrappedIoInput():read(...);
end;
wrappedIoWrite = function(...)
return wrappedIoOutput():write(...);
end;
wrappedOsRemove = function(fileName)
local realPath, err = toRealPath(fileName);
if not realPath then return nil, err; end;
local status = stdOsRemove(fileName);
if status then
return true;
else
return nil, "Error removing file '"..fileName.."'";
end;
end;
wrappedOsRename = function(oldName, newName)
oldName = stdToString(oldName);
newName = stdToString(newName);
if not oldName or not newName then
return stdError("Not a file name string");
end;
local oldPath, newPath, err;
oldPath, err = toRealPath(oldName);
if not oldPath then return nil, err; end;
newPath, err = toRealPath(newName);
if not newPath then return nil, err; end;
local status = stdOsRename(oldPath, newPath);
if status then
return true;
else
return nil, "Error renaming file '"..oldName.."' to '"..newName.."'";
end;
end;
end;
security.toRealPath = toRealPath;
sandboxEnv.io.close = std.io.close;
sandboxEnv.io.flush = std.io.flush;
sandboxEnv.io.input = wrappedIoInput;
sandboxEnv.io.lines = wrappedIoLines;
sandboxEnv.io.open = wrappedIoOpen;
sandboxEnv.io.output = wrappedIoOutput;
sandboxEnv.io.popen = nil; -- Removed completely
sandboxEnv.io.read = wrappedIoRead;
sandboxEnv.io.stderr = std.io.stderr;
sandboxEnv.io.stdin = std.io.stdin;
sandboxEnv.io.stdout = std.io.stdout;
sandboxEnv.io.tmpfile = std.io.tmpfile;
sandboxEnv.io.type = std.io.type;
sandboxEnv.io.write = wrappedIoWrite;
sandboxEnv.os.remove = wrappedOsRemove;
sandboxEnv.os.rename = wrappedOsRename;
sandboxEnv.os.tmpname = wrappedOsTmpName;
-- Code Generation
local wrappedDoFile;
local wrappedLoad;
local wrappedLoadFile;
local wrappedLoadString;
do
local DO_FILE_READ_LIMIT = 4096;
local stdDoFile = std.dofile;
local stdLoad = std.load;
local stdLoadFile = std.loadfile;
local stdLoadString = std.loadstring;
wrappedLoad = function(func, chunkName)
local chunk, err = stdLoad(func, chunkName);
if not chunk then return nil, err; end;
return stdSetFEnv(chunk, sandboxEnv);
end;
wrappedLoadFile = function(fileName)
if fileName == nil then
local chunk, err = stdLoadFile();
if not chunk then return nil, err; end;
return stdSetFEnv(chunk, sandboxEnv);
end;
fileName = stdToString(fileName);
if not fileName then return stdError("Not a file name string"); end;
local file, err = wrappedIoOpen(fileName, "r");
if not file then return nil, err; end;
local reader = function() return file:read(DO_FILE_READ_LIMIT); end;
local chunk, err = wrappedLoad(reader, fileName);
file:close();
return chunk, err;
end;
wrappedLoadString = function(str, chunkName)
local chunk, err = stdLoadString(str, chunkName);
if not chunk then return nil, err; end;
return stdSetFEnv(chunk, sandboxEnv);
end;
wrappedDoFile = function(fileName)
if fileName == nil then
return stdDoFile();
end;
local chunk, err = wrappedLoadFile(fileName);
if not chunk then return stdError(err); end;
return chunk();
end;
end;
sandboxEnv.dofile = wrappedDoFile;
sandboxEnv.load = wrappedLoad;
sandboxEnv.loadfile = wrappedLoadFile;
sandboxEnv.loadstring = wrappedLoadString;
-- OS Functionality
sandboxEnv.os.clock = std.os.clock;
sandboxEnv.os.date = std.os.date;
sandboxEnv.os.difftime = std.os.difftime;
sandboxEnv.os.execute = nil; -- Removed completely
sandboxEnv.os.exit = std.os.exit;
sandboxEnv.os.getenv = std.os.getenv;
-- sandboxEnv.os.remove -- Handled above
-- sandboxEnv.os.rename -- Handled above
sandboxEnv.os.setlocale = std.os.setlocale;
sandboxEnv.os.time = std.os.time;
-- sandboxEnv.os.tmpname -- Handled above
-- Metatables and Function Environments
--
-- Only metatables or function environments set in the sandboxed environment
-- will be returned to the sandboxed environment.
local wrappedGetMetatable;
local wrappedSetMetatable;
local wrappedGetFEnv;
local wrappedSetFEnv;
do
local stdGetMetatable = std.getmetatable;
local stdSetMetatable = std.setmetatable;
local stdGetFEnv = std.getfenv;
-- Weak keys
local modMetas = stdSetMetatable({}, { __mode = 'k' });
local modFEnvs = stdSetMetatable({}, { __mode = 'k' });
wrappedGetMetatable = function(object)
local meta = stdGetMetatable(object);
return (modMetas[meta] and meta) or nil;
end;
wrappedSetMetatable = function(object, meta)
local r = stdSetMetatable(object, meta);
if meta then modMetas[meta] = true; end;
return r;
end;
wrappedGetFEnv = function(f)
local e = stdGetFEnv(f);
return (modFEnvs[e] and e) or sandboxEnv;
end;
wrappedSetFEnv = function(f, env)
if not env then return stdSetFEnv(f, sandboxEnv); end;
local r = stdSetFEnv(f, env);
modFEnvs[env] = true;
return r;
end;
end;
sandboxEnv.getmetatable = wrappedGetMetatable;
sandboxEnv.setmetatable = wrappedSetMetatable;
sandboxEnv.getfenv = wrappedGetFEnv;
sandboxEnv.setfenv = wrappedSetFEnv;
-- Packages/Modules
local wrappedModule;
local wrappedRequire;
local wrappedSeeAll;
local sandboxSearcher;
local builinSearcher;
do
local function isName(str)
return str and #str > 0 and stdStrFind(str, "^[_%a][_%a%d]*$");
end;
local function packageModNameSplit(modName)
local pkgComps = split(modName, ".", true);
for _, nc in ipairs(pkgComps) do
if not isName(nc) then return nil, nil, modName; end;
end;
if #comps > 1 then
local pkgName = stdTabConcat(pkgComps, ".", 1, #comps - 1);
local simpleName = stdTabRemove(pkgComps);
return pkgName, pkgComps, simpleName;
else
return nil, comps, modName;
end;
end;
local function findPackage(loaded, pkgComps)
local p = loaded;
for _, n in ipairs(pkgComps) do
p = p[n];
if not p then break; end;
end;
return p;
end;
local function findOrCreatePackage(loaded, pkgComps)
local p = loaded;
for _, n in ipairs(pkgComps) do
local c = p[n];
if not c then c = {}; p[n] = c; end;
p = c;
end;
return p;
end;
local function findModule(loaded, modName, pkgComps, simpleName)
local mod = loaded[modName];
if mod or not pkgComps then return mod; end;
local pkg = findPackage(loaded, pkgComps);
return pkg and pkg[simpleName];
end;
local function storeModule(loaded, modName, pkgComps, simpleName, mod)
loaded[modName] = mod;
if pkgComps then
local pkg = findOrCreatePackage(loaded, pkgComps);
pkg[simpleName] = mod;
end;
end;
wrappedModule = function(modName, ...)
local loaded = sandboxEnv.package.loaded;
local pkgName, pkgComps, simpleName = packageModNameSplit(modName);
local mod = findModule(loaded, modName, pkgComps, simpleName);
if not mod then
mod = (isName(modName) and sandboxEnv[modName]) or {};
storeModule(loaded, modName, pkgComps, simpleName, mod);
end;
mod._NAME = modName;
mod._M = mod;
mod._PACKAGE = pkgName;
wrappedSetFEnv(2, mod);
for func in ipairs({ ... }) do func(mod); end;
end;
wrappedRequire = function(modName)
local loaded = sandboxEnv.package.loaded;
local pkgName, pkgComps, simpleName = packageModNameSplit(modName);
local mod = findModule(loaded, modName, pkgComps, simpleName);
if mod then return mod; end;
local loader = nil;
local reasons = {};
for _, searcher in ipairs(sandboxEnv.package.loaders) do
local result = searcher(modName);
local resultType = stdType(result);
if resultType == 'function' then
loader = result;
break;
elseif resultType == 'string' then
stdTabInsert(reasons, result);
end;
end;
if not loader then
local err = "Couldn't load module '" .. modName .. "'";
if #reasons > 0 then
err = err .. "\n" .. stdTabConcat(reasons, "\n");
end;
return stdError(err);
end;
mod = loader(modName);
if mod then
storeModule(loaded, modName, pkgComps, simpleName, mod);
else
mod = findModule(loaded, modName, pkgComps, simpleName);
if not mod then
mod = true;
storeModule(loaded, modName, pkgComps, simpleName, mod);
end;
end;
return mod;
end;
wrappedSeeAll = function(mod)
wrappedSetMetatable(mod, { __index = sandboxEnv });
end;
sandboxSearcher = function(modName)
return sandboxEnv.package.preload[modName];
end;
builinSearcher = function(modName)
local pkgName, pkgComps, simpleName = packageModNameSplit(modName);
local mod =
findModule(std.package.loaded, modName, pkgComps, simpleName);
if mod then
return function(name) return (name == modName and mod) or nil; end;
end;
local loader = nil;
local reasons = {};
for _, searcher in ipairs(std.package.loaders) do
local result = searcher(modName);
local resultType = stdType(result);
if resultType == 'function' then
loader = result;
break;
elseif resultType == 'string' then
stdTabInsert(reasons, result);
end;
end;
return loader or (#reasons > 0 and stdTabConcat(reasons, "\n")) or nil;
end;
end;
sandboxEnv.module = wrappedModule;
sandboxEnv.require = wrappedRequire;
sandboxEnv.package.config = std.package.config;
sandboxEnv.package.cpath = nil;
sandboxEnv.package.loaded = {};
sandboxEnv.package.loaders = { sandboxSearcher, builinSearcher };
sandboxEnv.package.loadlib = nil; -- Removed completely
sandboxEnv.package.path = "";
sandboxEnv.package.preload = {};
sandboxEnv.package.searchpath = nil; -- (luajit)
sandboxEnv.package.seeall = wrappedSeeAll;
sandboxEnv.package.loaded._G = sandboxEnv;
sandboxEnv.package.loaded.coroutine = sandboxEnv.coroutine;
sandboxEnv.package.loaded.io = sandboxEnv.io;
sandboxEnv.package.loaded.math = sandboxEnv.math;
sandboxEnv.package.loaded.os = sandboxEnv.os;
sandboxEnv.package.loaded.package = sandboxEnv.package;
sandboxEnv.package.loaded.string = sandboxEnv.string;
sandboxEnv.package.loaded.table = sandboxEnv.table;