режим предателя + UI референсы и DragonUI
This commit is contained in:
@@ -0,0 +1,827 @@
|
||||
-- RestrictedExecution.lua (Part of the new Secure Headers implementation)
|
||||
--
|
||||
-- This contains the necessary (and sufficient) code to support
|
||||
-- 'restricted execution' for code provided via attributes, intended
|
||||
-- for use in the secure header implementations. It provides a fairly
|
||||
-- isolated execution environment and is constructed to maintain local
|
||||
-- control over the important elements of the execution environment.
|
||||
--
|
||||
-- Daniel Stephens (iriel@vigilance-committee.org)
|
||||
-- Nevin Flanagan (alestane@comcast.net)
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- Localizes for functions that are frequently called or need to be
|
||||
-- assuredly un-replaceable or unhookable.
|
||||
local type = type;
|
||||
local error = error;
|
||||
local scrub = scrub;
|
||||
local issecure = issecure;
|
||||
local rawset = rawset;
|
||||
local setfenv = setfenv;
|
||||
local loadstring = loadstring;
|
||||
local setmetatable = setmetatable;
|
||||
local getmetatable = getmetatable;
|
||||
local pcall = pcall;
|
||||
local tostring = tostring;
|
||||
local next = next;
|
||||
local unpack = unpack;
|
||||
local newproxy = newproxy;
|
||||
local select = select;
|
||||
local wipe = wipe;
|
||||
local tonumber = tonumber;
|
||||
|
||||
local t_insert = table.insert;
|
||||
local t_maxn = table.maxn;
|
||||
local t_concat = table.concat;
|
||||
local t_sort = table.sort;
|
||||
local t_remove = table.remove;
|
||||
|
||||
local s_gsub = string.gsub;
|
||||
|
||||
local IsFrameHandle = IsFrameHandle;
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- RESTRICTED CLOSURES
|
||||
|
||||
local function SelfScrub(self)
|
||||
if (self ~= nil and IsFrameHandle(self)) then
|
||||
return self;
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
-- closure, err = BuildRestrictedClosure(body, env, signature)
|
||||
--
|
||||
-- body -- The function body (defaults to "")
|
||||
-- env -- The execution environment (defaults to {})
|
||||
-- signature -- The function signature (defaults to "self,...")
|
||||
--
|
||||
-- Returns the constructed closure, or nil and an error
|
||||
local function BuildRestrictedClosure(body, env, signature)
|
||||
body = tostring(body) or "";
|
||||
signature = tostring(signature) or "self,...";
|
||||
if (type(env) ~= "table") then
|
||||
env = {};
|
||||
end
|
||||
|
||||
if (body:match("function")) then
|
||||
-- NOTE - This is overzealous but it keeps it simple
|
||||
return nil, "The function keyword is not permitted";
|
||||
end
|
||||
|
||||
if (body:match("[{}]")) then
|
||||
-- NOTE - This is overzealous but it keeps it simple
|
||||
return nil, "Direct table creation is not permitted";
|
||||
end
|
||||
|
||||
if (signature:match("function")) then
|
||||
-- NOTE - This is overzealous but it keeps it simple
|
||||
return nil, "The function keyword is not permitted";
|
||||
end
|
||||
|
||||
if (not signature:match("^[a-zA-Z_0-9, ]*[.]*$")) then
|
||||
-- NOTE - This is overzealous but it keeps it simple
|
||||
return nil, "Signature contains invalid characters (" .. signature .. ")";
|
||||
end
|
||||
|
||||
-- Include a \n before end to stop shenanigans with comments
|
||||
local def, err =
|
||||
loadstring("return function (" .. signature .. ") " .. body .. "\nend", body);
|
||||
if (def == nil) then
|
||||
return nil, err;
|
||||
end
|
||||
|
||||
-- Use a completely empty environment here to be absolutely
|
||||
-- sure to avoid tampering during function definition.
|
||||
setfenv(def, {});
|
||||
def = def();
|
||||
-- Double check that the definition did infact return a function
|
||||
if (type(def) ~= "function") then
|
||||
return nil, "Invalid body";
|
||||
end
|
||||
-- Set the desired environment on the resulting closure.
|
||||
setfenv(def, env);
|
||||
|
||||
-- And then return a 'safe' wrapped invocation for the closure.
|
||||
return function(self, ...) return def(SelfScrub(self), scrub(...)) end;
|
||||
end
|
||||
|
||||
-- factory = CreateClosureFactory(env, signature)
|
||||
--
|
||||
-- env -- The desired environment table for the closures
|
||||
-- signature -- The function signature for the closures
|
||||
--
|
||||
-- Returns a 'factory table' which is keyed by function bodies, and
|
||||
-- has restricted closure values. It's weak-keyed and uses an __index
|
||||
-- metamethod that automatically creates necessary closures on demand.
|
||||
local function CreateClosureFactory(env, signature)
|
||||
local function metaIndex(t, k)
|
||||
if (type(k) == "string") then
|
||||
local closure, err = BuildRestrictedClosure(k, env, signature);
|
||||
if (not closure) then
|
||||
-- Put the error into a closure to avoid constantly
|
||||
-- re-parsing it
|
||||
err = tostring(err or "Closure creation failed");
|
||||
closure = function() error(err) end;
|
||||
end
|
||||
if (issecure()) then
|
||||
t[k] = closure;
|
||||
end
|
||||
return closure;
|
||||
end
|
||||
error("Invalid closure body type (" .. type(k) .. ")");
|
||||
return nil;
|
||||
end
|
||||
|
||||
local ret = {};
|
||||
setmetatable(ret, { __index = metaIndex, __mode = "k" });
|
||||
return ret;
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- RESTRICTED TABLES
|
||||
--
|
||||
-- Provides fully proxied tables with restrictions on their contents.
|
||||
--
|
||||
|
||||
-- Mapping table from restricted table 'proxy' to the 'real' storage
|
||||
local LOCAL_Restricted_Tables = {};
|
||||
setmetatable(LOCAL_Restricted_Tables, { __mode="k" });
|
||||
|
||||
-- Metatable common to all restricted tables (This introduces one
|
||||
-- level of indirection in every use as a means to share the same
|
||||
-- metatable between all instances)
|
||||
local LOCAL_Restricted_Table_Meta = {
|
||||
__index = function(t, k)
|
||||
local real = LOCAL_Restricted_Tables[t];
|
||||
return real[k];
|
||||
end,
|
||||
|
||||
__newindex = function(t, k, v)
|
||||
local real = LOCAL_Restricted_Tables[t];
|
||||
if (not issecure()) then
|
||||
error("Cannot insecurely modify restricted table");
|
||||
return;
|
||||
end
|
||||
local tv = type(v);
|
||||
if ((tv ~= "string") and (tv ~= "number")
|
||||
and (tv ~= "boolean") and (tv ~= "nil")
|
||||
and ((tv ~= "userdata")
|
||||
or not (LOCAL_Restricted_Tables[v]
|
||||
or IsFrameHandle(v)))) then
|
||||
error("Invalid value type '" .. tv .. "'");
|
||||
return;
|
||||
end
|
||||
local tk = type(k);
|
||||
if ((tk ~= "string") and (tk ~= "number")
|
||||
and (tk ~= "boolean")
|
||||
and ((tk ~= "userdata")
|
||||
or not (IsFrameHandle(k)))) then
|
||||
error("Invalid key type '" .. tk .. "'");
|
||||
return;
|
||||
end
|
||||
real[k] = v;
|
||||
end,
|
||||
|
||||
__len = function(t)
|
||||
local real = LOCAL_Restricted_Tables[t];
|
||||
return #real;
|
||||
end,
|
||||
|
||||
__metatable = false, -- False means read-write proxy
|
||||
}
|
||||
|
||||
local LOCAL_Readonly_Restricted_Tables = {};
|
||||
setmetatable(LOCAL_Readonly_Restricted_Tables, { __mode="k" });
|
||||
|
||||
local function CheckReadonlyValue(ret)
|
||||
if (type(ret) == "userdata") then
|
||||
if (LOCAL_Restricted_Tables[ret]) then
|
||||
if (getmetatable(ret)) then
|
||||
return ret;
|
||||
end
|
||||
return LOCAL_Readonly_Restricted_Tables[ret];
|
||||
end
|
||||
end
|
||||
return ret;
|
||||
end
|
||||
|
||||
-- Metatable common to all read-only restricted tables (This also introduces
|
||||
-- indirection so that a single metatable is viable)
|
||||
local LOCAL_Readonly_Restricted_Table_Meta = {
|
||||
__index = function(t, k)
|
||||
local real = LOCAL_Restricted_Tables[t];
|
||||
return CheckReadonlyValue(real[k]);
|
||||
end,
|
||||
|
||||
__newindex = function(t, k, v)
|
||||
error("Table is read-only");
|
||||
end,
|
||||
|
||||
__len = function(t)
|
||||
local real = LOCAL_Restricted_Tables[t];
|
||||
return #real;
|
||||
end,
|
||||
|
||||
__metatable = true, -- True means read-only proxy
|
||||
}
|
||||
|
||||
|
||||
local LOCAL_Restricted_Prototype = newproxy(true);
|
||||
local LOCAL_Readonly_Restricted_Prototype = newproxy(true);
|
||||
do
|
||||
local meta = getmetatable(LOCAL_Restricted_Prototype);
|
||||
for k, v in pairs(LOCAL_Restricted_Table_Meta) do
|
||||
meta[k] = v;
|
||||
end
|
||||
|
||||
meta = getmetatable(LOCAL_Readonly_Restricted_Prototype);
|
||||
for k, v in pairs(LOCAL_Readonly_Restricted_Table_Meta) do
|
||||
meta[k] = v;
|
||||
end
|
||||
end
|
||||
|
||||
local function RestrictedTable_Readonly_index(t, k)
|
||||
local real = LOCAL_Restricted_Tables[k];
|
||||
if (not real) then return; end
|
||||
if (not issecure()) then
|
||||
error("Cannot create restricted tables from insecure code");
|
||||
return;
|
||||
end
|
||||
|
||||
local ret = newproxy(LOCAL_Readonly_Restricted_Prototype);
|
||||
LOCAL_Restricted_Tables[ret] = real;
|
||||
t[k] = ret;
|
||||
return ret;
|
||||
end
|
||||
|
||||
getmetatable(LOCAL_Readonly_Restricted_Tables).__index
|
||||
= RestrictedTable_Readonly_index;
|
||||
|
||||
-- table = RestrictedTable_create(...)
|
||||
--
|
||||
-- Create a new, restricted table, populating it from ... if
|
||||
-- necessary, similar to the way the normal table constructor
|
||||
-- works.
|
||||
local function RestrictedTable_create(...)
|
||||
local ret = newproxy(LOCAL_Restricted_Prototype);
|
||||
if (not issecure()) then
|
||||
error("Cannot create restricted tables from insecure code");
|
||||
return;
|
||||
end
|
||||
LOCAL_Restricted_Tables[ret] = {};
|
||||
|
||||
-- Use this loop to ensure that the contents of the new
|
||||
-- table are all allowed
|
||||
for i = 1, select('#', ...) do
|
||||
ret[i] = select(i, ...);
|
||||
end
|
||||
|
||||
return ret;
|
||||
end
|
||||
|
||||
-- table = GetReadonlyRestrictedTable(otherTable)
|
||||
--
|
||||
-- Given a restricted table, return a read-only proxy to the same
|
||||
-- table (which will be itself, if it's already read-only)
|
||||
function GetReadonlyRestrictedTable(ref)
|
||||
if (LOCAL_Restricted_Tables[ref]) then
|
||||
if (getmetatable(ref)) then
|
||||
return ref;
|
||||
end
|
||||
return LOCAL_Readonly_Restricted_Tables[ref];
|
||||
end
|
||||
error("Invalid restricted table");
|
||||
end
|
||||
|
||||
-- key = RestrictedTable_next(table [,key])
|
||||
--
|
||||
-- An implementation of the next function, which is
|
||||
-- aware of restricted and normal tables and behaves consistently
|
||||
-- for both.
|
||||
local function RestrictedTable_next(T, k)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
local idx, val = next(PT, k);
|
||||
if (val ~= nil) then
|
||||
return idx, CheckReadonlyValue(val);
|
||||
else
|
||||
return idx, val;
|
||||
end
|
||||
else
|
||||
return next(PT, k);
|
||||
end
|
||||
end
|
||||
return next(T, k);
|
||||
end
|
||||
|
||||
-- iterfunc, table, key = RestrictedTable_pairs(table)
|
||||
--
|
||||
-- An implementation of the pairs function, which is aware
|
||||
-- of and iterates over both restricted and normal tables.
|
||||
local function RestrictedTable_pairs(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
-- v
|
||||
return RestrictedTable_next, T, nil;
|
||||
end
|
||||
return pairs(T);
|
||||
end
|
||||
|
||||
-- key, value = RestrictedTable_ipairsaux(table, prevkey)
|
||||
--
|
||||
-- Iterator function for internal use by restricted table ipairs
|
||||
local function RestrictedTable_ipairsaux(T, i)
|
||||
i = i + 1;
|
||||
local v = T[i];
|
||||
if (v) then
|
||||
return i, v;
|
||||
end
|
||||
end
|
||||
|
||||
-- iterfunc, table, key = RestrictedTable_ipairs(table)
|
||||
--
|
||||
-- An implementation of the ipairs function, which is aware
|
||||
-- of and iterates over both restricted and normal tables.
|
||||
local function RestrictedTable_ipairs(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
return RestrictedTable_ipairsaux, T, 0;
|
||||
end
|
||||
return ipairs(T);
|
||||
end
|
||||
|
||||
-- Recursive helper to ensure all values from a list are properly converted
|
||||
-- to read-only proxies
|
||||
local RestrictedTable_unpack_ro;
|
||||
function RestrictedTable_unpack_ro(...)
|
||||
local n = select('#', ...);
|
||||
if (n == 0) then
|
||||
return;
|
||||
end
|
||||
return CheckReadonlyValue(...), RestrictedTable_unpack_ro(select(2, ...));
|
||||
end
|
||||
|
||||
-- ... = RestrictedTable_unpack(table)
|
||||
--
|
||||
-- An implementation of unpack which unpacks both restricted
|
||||
-- and normal tables.
|
||||
local function RestrictedTable_unpack(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
return RestrictedTable_unpack_ro(unpack(PT));
|
||||
end
|
||||
return unpack(PT)
|
||||
end
|
||||
return unpack(T);
|
||||
end
|
||||
|
||||
-- table = RestrictedTable_wipe(table)
|
||||
--
|
||||
-- A restricted aware implementation of wipe()
|
||||
local function RestrictedTable_wipe(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
error("Cannot wipe a read-only table");
|
||||
return;
|
||||
end
|
||||
if (not issecure()) then
|
||||
error("Cannot insecurely modify restricted table");
|
||||
return;
|
||||
end
|
||||
wipe(PT);
|
||||
return T;
|
||||
end
|
||||
return wipe(T);
|
||||
end
|
||||
|
||||
-- RestrictedTable_maxn(table)
|
||||
--
|
||||
-- A restricted aware implementation of table.maxn()
|
||||
local function RestrictedTable_maxn(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
return t_maxn(PT);
|
||||
end
|
||||
return t_maxn(T);
|
||||
end
|
||||
|
||||
-- RestrictedTable_concat(table)
|
||||
--
|
||||
-- A restricted aware implementation of table.concat()
|
||||
local function RestrictedTable_concat(T)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
return t_concat(PT);
|
||||
end
|
||||
return t_concat(T);
|
||||
end
|
||||
|
||||
-- RestrictedTable_sort(table, func)
|
||||
--
|
||||
-- A restricted aware implementation of table.sort()
|
||||
local function RestrictedTable_sort(T, func)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
error("Cannot sort a read-only table");
|
||||
return;
|
||||
end
|
||||
if (not issecure()) then
|
||||
error("Cannot insecurely modify restricted table");
|
||||
return;
|
||||
end
|
||||
t_sort(PT, func);
|
||||
return;
|
||||
end
|
||||
t_sort(T, func);
|
||||
end
|
||||
|
||||
-- RestrictedTable_insert(table [,pos], val)
|
||||
--
|
||||
-- A restricted aware implementation of table.insert()
|
||||
local function RestrictedTable_insert(T, ...)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
error("Cannot insert into a read-only table");
|
||||
return;
|
||||
end
|
||||
local pos, val;
|
||||
if (select('#', ...) == 1) then
|
||||
local val = ...;
|
||||
T[#PT + 1] = val;
|
||||
return;
|
||||
end
|
||||
pos, val = ...;
|
||||
pos = tonumber(pos);
|
||||
t_insert(PT, pos, nil);
|
||||
-- Leverage protections present on regular indexing
|
||||
T[pos] = val;
|
||||
return;
|
||||
end
|
||||
return t_insert(T, ...);
|
||||
end
|
||||
|
||||
-- val = RestrictedTable_remove(table, pos)
|
||||
--
|
||||
-- A restricted aware implementation of table.remove()
|
||||
local function RestrictedTable_remove(T, pos)
|
||||
local PT = LOCAL_Restricted_Tables[T];
|
||||
if (PT) then
|
||||
if (getmetatable(T)) then
|
||||
error("Cannot remove from a read-only table");
|
||||
return;
|
||||
end
|
||||
if (not issecure()) then
|
||||
error("Cannot insecurely modify restricted table");
|
||||
return;
|
||||
end
|
||||
return CheckReadonlyValue(t_remove(PT, pos));
|
||||
end
|
||||
return t_remove(T, pos);
|
||||
end
|
||||
|
||||
-- objtype = RestrictedTable_type(obj)
|
||||
--
|
||||
-- A version of type which returns 'table' for restricted tables
|
||||
local function RestrictedTable_type(obj)
|
||||
local t = type(obj);
|
||||
if (t == "userdata") then
|
||||
if (LOCAL_Restricted_Tables[obj]) then
|
||||
t = "table";
|
||||
end
|
||||
end
|
||||
return t;
|
||||
end
|
||||
|
||||
-- ns = RestrictedTable_rtgsub(s, pattern, repl, n)
|
||||
--
|
||||
-- A version of string.gsub which is able to be passed restricted tables
|
||||
local function RestrictedTable_rtgsub(s, pattern, repl, n)
|
||||
local t = type(repl);
|
||||
if (t == "userdata") then
|
||||
local PT = LOCAL_Restricted_Tables[repl];
|
||||
return s_gsub(s, pattern, PT, n);
|
||||
end
|
||||
return s_gsub(s, pattern, repl, n);
|
||||
end
|
||||
|
||||
-- Export these functions so that addon code can use them if desired
|
||||
-- and so that the handlers can create these tables
|
||||
rtable = {
|
||||
next = RestrictedTable_next;
|
||||
pairs = RestrictedTable_pairs;
|
||||
ipairs = RestrictedTable_ipairs;
|
||||
unpack = RestrictedTable_unpack;
|
||||
newtable = RestrictedTable_create;
|
||||
|
||||
maxn = RestrictedTable_maxn;
|
||||
insert = RestrictedTable_insert;
|
||||
remove = RestrictedTable_remove;
|
||||
sort = RestrictedTable_sort;
|
||||
concat = RestrictedTable_concat;
|
||||
wipe = RestrictedTable_wipe;
|
||||
|
||||
type = RestrictedTable_type;
|
||||
rtgsub = RestrictedTable_rtgsub;
|
||||
};
|
||||
|
||||
-- Add this version of gsub to the string metatable
|
||||
string.rtgsub = RestrictedTable_rtgsub;
|
||||
|
||||
local LOCAL_Table_Namespace = {
|
||||
table = {
|
||||
maxn = RestrictedTable_maxn;
|
||||
insert = RestrictedTable_insert;
|
||||
remove = RestrictedTable_remove;
|
||||
sort = RestrictedTable_sort;
|
||||
concat = RestrictedTable_concat;
|
||||
wipe = RestrictedTable_wipe;
|
||||
new = RestrictedTable_create;
|
||||
}
|
||||
};
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- RESTRICTED ENVIRONMENT
|
||||
--
|
||||
-- environment, manageFunc = CreateRestrictedEnvironment(baseEnvironment)
|
||||
--
|
||||
-- baseEnvironment -- The base environment table (containing functions)
|
||||
--
|
||||
-- environment -- The new restricted environment table
|
||||
-- manageFunc -- Management function to set/clear working and proxy
|
||||
-- environments.
|
||||
--
|
||||
-- The management function takes two parameters
|
||||
-- manageFunc(set, workingTable, controlHandle)
|
||||
--
|
||||
-- set is a boolean; If it's true then the working table is set to the
|
||||
-- specified value (pushing any previous environment onto a stack). If
|
||||
-- it's false then the working table is unset (and restored from stack).
|
||||
--
|
||||
-- The working table should be a restricted table, or an immutable object.
|
||||
--
|
||||
-- The controlHandle is an optional object which is made available to
|
||||
-- the restricted code with the name 'control'.
|
||||
--
|
||||
-- The management function monitors calls to get and set in order to prevent
|
||||
-- re-entrancy (i.e. calls to get and set must be balanced, and one cannot
|
||||
-- switch working tables in the middle).
|
||||
local function CreateRestrictedEnvironment(base)
|
||||
if (type(base) ~= "table") then base = {}; end
|
||||
|
||||
local working, control, depth = nil, nil, 0;
|
||||
local workingStack, controlStack = {}, {};
|
||||
|
||||
local result = {};
|
||||
local meta_index;
|
||||
|
||||
local function meta_index(t, k)
|
||||
local v = base[k] or working[k];
|
||||
if (v == nil) then
|
||||
if (k == "control") then return control; end
|
||||
end
|
||||
return v;
|
||||
end;
|
||||
|
||||
local function meta_newindex(t, k, v)
|
||||
working[k] = v;
|
||||
end
|
||||
|
||||
local meta = {
|
||||
__index = meta_index,
|
||||
__newindex = meta_newindex,
|
||||
__metatable = false;
|
||||
__environment = false;
|
||||
}
|
||||
|
||||
setmetatable(result, meta);
|
||||
|
||||
local function manage(set, newWorking, newControl)
|
||||
if (set) then
|
||||
if (depth == 0) then
|
||||
depth = 1;
|
||||
else
|
||||
workingStack[depth] = working;
|
||||
controlStack[depth] = control;
|
||||
|
||||
depth = depth + 1;
|
||||
end
|
||||
working = newWorking;
|
||||
control = newControl;
|
||||
else
|
||||
if (depth == 0) then
|
||||
error("Attempted to release unused environment");
|
||||
return;
|
||||
end
|
||||
|
||||
if (working ~= newWorking) then
|
||||
error("Working environment mismatch at release");
|
||||
return;
|
||||
end
|
||||
|
||||
if (control ~= newControl) then
|
||||
error("Control handle mismatch at release");
|
||||
return;
|
||||
end
|
||||
|
||||
depth = depth - 1;
|
||||
if (depth == 0) then
|
||||
working = nil;
|
||||
control = nil;
|
||||
else
|
||||
working = workingStack[depth];
|
||||
control = controlStack[depth];
|
||||
workingStack[depth] = nil;
|
||||
controlStack[depth] = nil;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result, manage;
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- AVAILABLE FUNCTIONS
|
||||
--
|
||||
-- The current implementation has a single set of functions (aka a single
|
||||
-- base environment), this initializes that environment by creating a master
|
||||
-- base environment table, and then creating a restricted environment
|
||||
-- around that. If the RESTRICTED_FUNCTIONS_SCOPE table exists when
|
||||
-- this file is executed then its contents are added to the environment.
|
||||
--
|
||||
-- It is expected that any functions that are to be placed into this
|
||||
-- environment have been carefully written to not return arbtirary tables,
|
||||
-- functions, or userdata back to the caller.
|
||||
--
|
||||
-- One can use function(...) return scrub(realFunction(...)) end to wrap
|
||||
-- those functions one doesn't trust.
|
||||
|
||||
-- A metatable to prevent tampering with the environment tables.
|
||||
local LOCAL_No_Secure_Update_Meta = {
|
||||
__newindex = function() end;
|
||||
__metatable = false;
|
||||
};
|
||||
|
||||
local LOCAL_Restricted_Global_Functions = {
|
||||
newtable = RestrictedTable_create;
|
||||
pairs = RestrictedTable_pairs;
|
||||
ipairs = RestrictedTable_ipairs;
|
||||
next = RestrictedTable_next;
|
||||
unpack = RestrictedTable_unpack;
|
||||
|
||||
-- Table methods
|
||||
wipe = RestrictedTable_wipe;
|
||||
tinsert = RestrictedTable_insert;
|
||||
tremove = RestrictedTable_remove;
|
||||
|
||||
-- Synthetic restricted-table-aware 'type'
|
||||
type = RestrictedTable_type;
|
||||
|
||||
-- Restricted table aware gsub
|
||||
rtgsub = RestrictedTable_rtgsub;
|
||||
};
|
||||
|
||||
-- A helper function to recursively copy and protect scopes
|
||||
local PopulateGlobalFunctions
|
||||
function PopulateGlobalFunctions(src, dest)
|
||||
for k, v in pairs(src) do
|
||||
if (type(k) == "string") then
|
||||
local tv = type(v);
|
||||
if ((tv == "function") or (tv == "number") or (tv == "string") or (tv == "boolean")) then
|
||||
dest[k] = v;
|
||||
elseif (tv == "table") then
|
||||
local subdest = {};
|
||||
PopulateGlobalFunctions(v, subdest);
|
||||
setmetatable(subdest, LOCAL_No_Secure_Update_Meta);
|
||||
local dproxy = newproxy(true);
|
||||
local dproxy_meta = getmetatable(dproxy);
|
||||
dproxy_meta.__index = subdest;
|
||||
dproxy_meta.__metatable = false;
|
||||
dest[k] = dproxy;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Import any functions initialized by other/earier files
|
||||
if (RESTRICTED_FUNCTIONS_SCOPE) then
|
||||
PopulateGlobalFunctions(RESTRICTED_FUNCTIONS_SCOPE,
|
||||
LOCAL_Restricted_Global_Functions);
|
||||
RESTRICTED_FUNCTIONS_SCOPE = nil;
|
||||
end
|
||||
|
||||
PopulateGlobalFunctions(LOCAL_Table_Namespace,
|
||||
LOCAL_Restricted_Global_Functions);
|
||||
|
||||
-- Create the environment
|
||||
local LOCAL_Function_Environment, LOCAL_Function_Environment_Manager =
|
||||
CreateRestrictedEnvironment(LOCAL_Restricted_Global_Functions);
|
||||
|
||||
-- Protect from injection via the string metatable index
|
||||
-- Assume for now that 'string' is relatively clean
|
||||
local strmeta = getmetatable("x");
|
||||
local newmetaindex = {};
|
||||
for k, v in pairs(string) do newmetaindex[k] = v; end
|
||||
setmetatable(newmetaindex, {
|
||||
__index = function(t,k)
|
||||
if (not issecure()) then
|
||||
return string[k];
|
||||
end
|
||||
end;
|
||||
__metatable = false;
|
||||
});
|
||||
strmeta.__index = newmetaindex;
|
||||
strmeta.__metatable = string;
|
||||
strmeta = nil;
|
||||
newmetaindex = nil;
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- CLOSURE FACTORIES
|
||||
--
|
||||
-- An automatically populating table keyed by function signature with
|
||||
-- values that are closure factories for those signatures.
|
||||
|
||||
local LOCAL_Closure_Factories = { };
|
||||
|
||||
local function ClosureFactories_index(t, signature)
|
||||
if (type(signature) ~= "string") then
|
||||
return;
|
||||
end
|
||||
|
||||
local factory = CreateClosureFactory(LOCAL_Function_Environment, signature);
|
||||
|
||||
if (not issecure()) then
|
||||
error("Cannot declare closure factories from insecure code");
|
||||
return;
|
||||
end
|
||||
|
||||
t[signature] = factory;
|
||||
return factory;
|
||||
end
|
||||
|
||||
setmetatable(LOCAL_Closure_Factories, { __index = ClosureFactories_index });
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- FUNCTION CALL
|
||||
|
||||
-- A helper method to release the restricted environment environment before
|
||||
-- returning from the function call.
|
||||
local function ReleaseAndReturn(workingEnv, ctrlHandle, pcallFlag, ...)
|
||||
-- Tampering at this point will irrevocably taint the protected
|
||||
-- environment, for now that's a handy protective measure.
|
||||
LOCAL_Function_Environment_Manager(false, workingEnv, ctrlHandle);
|
||||
if (pcallFlag) then
|
||||
return ...;
|
||||
end
|
||||
error("Call failed: " .. tostring( (...) ) );
|
||||
end
|
||||
|
||||
-- ? = CallRestrictedClosure(signature, workingEnv, onupdate, body, ...)
|
||||
--
|
||||
-- Invoke a managed closure, looking its definition up from a factory
|
||||
-- and managing its environment during execution.
|
||||
--
|
||||
-- signature -- function signature
|
||||
-- workingEnv -- the working environment, must be a restricted table
|
||||
-- ctrlHandle -- a control handle
|
||||
-- body -- function body
|
||||
-- ... -- any arguments to pass to the executing closure
|
||||
--
|
||||
-- Returns whatever the restricted closure returns
|
||||
function CallRestrictedClosure(signature, workingEnv, ctrlHandle, body, ...)
|
||||
if (not LOCAL_Restricted_Tables[workingEnv]) then
|
||||
error("Invalid working environment");
|
||||
return;
|
||||
end
|
||||
|
||||
signature = tostring(signature);
|
||||
local factory = LOCAL_Closure_Factories[signature];
|
||||
if (not factory) then
|
||||
error("Invalid signature '" .. signature .. "'");
|
||||
return;
|
||||
end
|
||||
|
||||
local closure = factory[body];
|
||||
if (not closure) then
|
||||
-- Expect factory to have thrown an error
|
||||
return;
|
||||
end
|
||||
|
||||
if (not issecure()) then
|
||||
error("Cannot call restricted closure from insecure code");
|
||||
return;
|
||||
end
|
||||
|
||||
if (type(ctrlHandle) ~= "userdata") then
|
||||
ctrlHandle = nil;
|
||||
end
|
||||
|
||||
LOCAL_Function_Environment_Manager(true, workingEnv, ctrlHandle);
|
||||
return ReleaseAndReturn(workingEnv, ctrlHandle, pcall( closure, ... ) );
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user