1
0
forked from redo/BlockLua
Files
BlockLua/src/lua-env-safe.lua

151 lines
4.9 KiB
Lua

-- Sanitize the Lua environment to:
-- Prevent scripts from accessing files outside the game directory
-- Prevent usage of libraries other than
-- Utility: Convert a list of strings into a map of string->true
local function tmap(t)
local u = {}; for _, n in ipairs(t) do u[n] = true end; return u;
end
-- Save banned global variables for wrapping with safe functions
local old_io = io
local old_require = require
local old_os = os
local old_debug = debug
local old_package = package
local old_allowffi = _bllua_allowffi
-- Remove all global variables except a whitelist
local ok_names = tmap {
'_G', '_bllua_ts', '_bllua_on_unload', '_bllua_on_error',
'string', 'table', 'math', 'coroutine', 'bit',
'pairs', 'ipairs', 'next', 'unpack', 'select',
'error', 'assert', 'pcall', 'xpcall',
'type', 'tostring', 'tonumber',
'loadstring',
'getmetatable', 'setmetatable',
'rawget', 'rawset', 'rawequal', 'rawlen',
'module', '_VERSION',
}
local not_ok_names = {}
for n, _ in pairs(_G) do
if not ok_names[n] then
table.insert(not_ok_names, n)
end
end
for _, n in ipairs(not_ok_names) do
_G[n] = nil
end
-- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing
-- modules/lualib is also allowed as read-only
local allowed_dirs = tmap {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
}
-- List of disallowed file extensions - basically executable file extensions
-- Note that even without this protection, exploiting would still require somehow
-- getting a file within the allowed directories to autorun,
-- so this is just a precaution.
local disallowed_exts = tmap {
-- windows
'bat', 'bin', 'cab', 'cmd', 'com', 'cpl', 'ex_', 'exe', 'gadget', 'inf', 'ins', 'inx', 'isu',
'job', 'jse', 'lnk', 'msc', 'msi', 'msp', 'mst', 'paf', 'pif', 'ps1', 'reg', 'rgs', 'scr',
'sct', 'shb', 'shs', 'u3p', 'vb', 'vbe', 'vbs', 'vbscript', 'ws', 'wsf', 'wsh',
-- linux
'csh', 'ksh', 'out', 'run', 'sh',
-- mac/other
'action', 'apk', 'app', 'command', 'ipa', 'osx', 'prg', 'workflow',
}
-- Arguments: file name (relative to game directory), boolean true if only reading
-- Return: clean file path if allowed (or nil if disallowed),
-- error string (or nil if allowed)
local function safe_path(fn, readonly)
if type(fn) ~= 'string' then
return nil, 'Filename must be a string'
end
fn = fn:gsub('\\', '/')
fn = fn:gsub('^ +', '')
fn = fn:gsub(' +$', '')
-- whitelist characters
local ic = fn:find('[^a-zA-Z0-9_%-/ %.]')
if ic then
return nil, 'Filename \'' .. fn .. '\' contains invalid character \'' ..
fn:sub(ic, ic) .. '\' at position ' .. ic
end
-- disallow up-dirs, absolute paths, and relative paths
-- './' and '../' are possible in scripts, because they're processed into
-- absolute paths in util.lua before reaching here
if fn:find('^%.') or fn:find('%.%.') or fn:find(':') or fn:find('^/') then
return nil, 'Filename \'' .. fn .. '\' contains invalid sequence'
end
-- allow only whitelisted dirs
local dir = fn:match('^([^/]+)/')
if not (dir and (
allowed_dirs[dir:lower()] or
(readonly and fn:find('^modules/lualib/'))))
then
return nil, 'File is in disallowed directory ' .. (dir or 'nil')
end
-- disallow blacklisted extensions
local ext = fn:match('%.([^/%.]+)$')
if ext and disallowed_exts[ext:lower()] then
return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' ..
(ext or '') .. '\''
end
return fn, nil
end
-- Wrap io.open with path sanitization
function _bllua_io_open(fn, md)
md = md or 'r'
local readonly = md == 'r' or md == 'rb'
local fns, err = safe_path(fn, readonly)
if fns then
return old_io.open(fns, md)
else
return nil, err
end
end
-- Allow io.type (works on file handles returned by io.open)
function _bllua_io_type(f)
return old_io.type(f)
end
-- Wrap require with a blacklist for unsafe built-in modules
-- List of allowed packages to require
-- Note that util.lua wraps this and provides 'require',
-- only falling back here if the package is not found in user files
local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts',
}
if old_allowffi then disallowed_packages['ffi'] = nil end
function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then
error('require: package name contains invalid character', 3)
elseif disallowed_packages[name] then
error('require: attempt to require disallowed module \'' .. name .. '\'', 3)
else
-- todo: reimplement require to not use package.* stuff?
return old_require(name)
end
end
package = {
seeall = old_package.seeall,
}
-- Provide limited debug
debug = {
traceback = old_debug.traceback,
getinfo = old_debug.getinfo,
getfilename = old_debug.getfilename, -- defined in lua.env.lua
}
print = _bllua_ts.echo
print(' Executed bllua-env-safe.lua')