在Lua 5.2中保护全局环境

转自:http://blog.csdn.net/axx1611/article/details/8121740

Lua脚本语言十分强大,但是有一个问题就是全局可写,比如你定义一个全局变量很容易不小心被另一个同名变量给覆盖掉。

这种问题一旦出现是十分难以调查的,该文章介绍的这种机制可以解决该问题。


我已经在我自己的工程中应用了该技术,它可以达到以下目的:

1.全局变量不能直接在Lua中被修改

2.可以创建出不能直接被修改的table

3.屏蔽一些你不想开放的Lua原生函数比如文件操作


注:我是混合着使用C和Lua实现该机制的,但是在纯Lua里也可以同样实现。为了便于表述,我这里只给出纯Lua版的例子。

另外该范例代码仅限于Lua 5.2版,但是该技巧同样可以适用于其他版本,但可能需要修改该一部分代码。


首先将所有安全机制的代码放进一个Lua脚本文件safe.lua如下:

[plain]  view plain  copy
  1. -- 仅支持Lua 5.2版  
  2. assert(_VERSION == "Lua 5.2")  
  3.   
  4. -- 全局环境在注册表中的索引值(见lua.h)  
  5. local LUA_RIDX_GLOBALS = 2  
  6.   
  7. -- 安全table的metatable标志  
  8. local SAFE_TABLE_FLAG = ".SAFETABLE"  
  9.   
  10. -- 设置全局安全保护机制  
  11. local function SetupGlobal()  
  12.   
  13.     -- 获取注册表  
  14.     local reg = debug.getregistry()  
  15.   
  16.     local env = {}          -- 新环境table  
  17.     local proxy = {}        -- 代理table  
  18.     local mt = {}           -- metatable  
  19.   
  20.     -- 操作重载  
  21.     mt.__index = proxy  
  22.     mt.__newindex = function() print("cannot modify global enviroment!") end  
  23.     mt.__len = function() return #proxy end  
  24.     mt.__pairs = function() return pairs(proxy) end  
  25.     mt.__ipairs = function() return ipairs(proxy) end  
  26.   
  27.     -- 隐藏metatable  
  28.     mt.__metatable = 0  
  29.   
  30.     -- 标记为安全table  
  31.     mt[SAFE_TABLE_FLAG] = true  
  32.   
  33.     -- 获取旧环境  
  34.     local old_env = reg[LUA_RIDX_GLOBALS]  
  35.   
  36.     -- 设置新环境的metatable  
  37.     setmetatable(env, mt)  
  38.   
  39.     -- 启用新环境  
  40.     _ENV = env  
  41.   
  42.     -- 将全局默认环境也改为新环境  
  43.     reg[LUA_RIDX_GLOBALS] = env  
  44.   
  45.     -- 返回代理table和旧环境  
  46.     return proxy, old_env  
  47.   
  48. end  
  49.   
  50. -- 新建一个有安全保护的table  
  51. local function CreateSafeTable(base)  
  52.   
  53.     local new = {}          -- 新table  
  54.     local mt = {}           -- metatable  
  55.   
  56.     -- 如果没有指定base则新建一个空table  
  57.     local proxy = (type(base) == "table") and base or {}  
  58.   
  59.     -- 操作重载  
  60.     mt.__index = proxy  
  61.     mt.__newindex = function() print("cannot modify safe table!") end  
  62.     mt.__len = function() return #proxy end  
  63.     mt.__pairs = function() return pairs(proxy) end  
  64.     mt.__ipairs = function() return ipairs(proxy) end  
  65.   
  66.     -- 隐藏metatable  
  67.     mt.__metatable = 0  
  68.   
  69.     -- 标记为安全table  
  70.     mt[SAFE_TABLE_FLAG] = true  
  71.   
  72.     -- 设置新table的metatable  
  73.     setmetatable(new, mt)  
  74.   
  75.     -- 返回新table和对应的代理table  
  76.     return new, proxy  
  77.   
  78. end  
  79.   
  80. -- 开启全局保护  
  81. local proxy, old_env = SetupGlobal()  
  82.   
  83. -- 在这里复制需要导出给新环境使用的Lua原生全局变量和函数  
  84. -- 被屏蔽的原生全局变量和函数有:  
  85. --  _G          Lua 5.2推荐使用_ENV(你可以根据需要把它定义为_ENV)  
  86. --  dofile      我的工程需要屏遮文件系统,我没有评估过开放它对安全性有没有影响  
  87. --  loadfile    我的工程需要屏遮文件系统,我没有评估过开放它对安全性有没有影响  
  88. --  rawequal    需要覆盖,不应该直接操作安全table  
  89. --  rawget      需要覆盖,不应该直接操作安全table  
  90. --  rawlen      需要覆盖,不应该直接操作安全table  
  91. --  rawset      需要覆盖,不应该直接操作安全table  
  92. --  require     我的工程需要屏遮文件系统,我没有评估过开放它对安全性有没有影响  
  93. proxy._VERSION = old_env._VERSION  
  94. proxy.assert = old_env.assert  
  95. proxy.collectgarbage = old_env.collectgarbage  
  96. proxy.error = old_env.error  
  97. proxy.getmetatable = old_env.getmetatable  
  98. proxy.ipairs = old_env.ipairs  
  99. proxy.load = old_env.load  
  100. proxy.next = old_env.next  
  101. proxy.pairs = old_env.pairs  
  102. proxy.pcall = old_env.pcall  
  103. proxy.print = old_env.print  
  104. proxy.select = old_env.select  
  105. proxy.setmetatable = old_env.setmetatable  
  106. proxy.tostring = old_env.tostring  
  107. proxy.tonumber = old_env.tonumber  
  108. proxy.type = old_env.type  
  109. proxy.xpcall = old_env.xpcall  
  110.   
  111. -- 在这里导出给新环境使用的Lua原生全局table(将被设为只读table)  
  112. -- 被屏蔽的原生全局table有:  
  113. --  coroutine   我的工程里不需要coroutine,我没有评估过开放它对安全性有没有影响  
  114. --  debug       会严重影响安全性,必须屏蔽  
  115. --  io          我的工程需要屏遮文件系统,我没有评估过开放它对安全性有没有影响  
  116. --  os          我的工程里不需要os,我没有评估过开放它对安全性有没有影响  
  117. --  package     我的工程需要屏遮文件系统,我没有评估过开放它对安全性有没有影响  
  118. proxy.bit32 = CreateSafeTable(old_env.bit32)  
  119. proxy.math = CreateSafeTable(old_env.math)  
  120. proxy.string = CreateSafeTable(old_env.string)  
  121. proxy.table = CreateSafeTable(old_env.table)  
  122.   
  123. -- 实现安全版的rawequal  
  124. proxy.rawequal = function(v1, v2)  
  125.   
  126.     -- 获得真实的metatable  
  127.     local mt1 = old_env.debug.getmetatable(v1)  
  128.     local mt2 = old_env.debug.getmetatable(v2)  
  129.   
  130.     -- 如果是安全table则使用代理table  
  131.     if mt1 and mt1[SAFE_TABLE_FLAG] then  
  132.         v1 = mt1.__index  
  133.     end  
  134.     if mt2 and mt2[SAFE_TABLE_FLAG] then  
  135.         v2 = mt2.__index  
  136.     end  
  137.   
  138.     -- 调用原始rawequal  
  139.     return old_env.rawequal(v1, v2)  
  140.   
  141. end  
  142.   
  143. -- 实现安全版的rawget  
  144. proxy.rawget = function(t, k)  
  145.   
  146.     -- 获得真实的metatable  
  147.     local mt = old_env.debug.getmetatable(t)  
  148.   
  149.     -- 如果是安全table则使用代理table  
  150.     if mt and mt[SAFE_TABLE_FLAG] then  
  151.         t = mt.__index  
  152.     end  
  153.   
  154.     -- 调用原始rawget  
  155.     return old_env.rawget(t, k)  
  156.   
  157. end  
  158.   
  159. -- 实现安全版的rawlen  
  160. proxy.rawlen = function(v)  
  161.   
  162.     -- 获得真实的metatable  
  163.     local mt = old_env.debug.getmetatable(v)  
  164.   
  165.     -- 如果是安全table则使用代理table  
  166.     if mt and mt[SAFE_TABLE_FLAG] then  
  167.         v = mt.__index  
  168.     end  
  169.   
  170.     -- 调用原始rawlen  
  171.     return old_env.rawlen(v)  
  172.   
  173. end  
  174.   
  175. -- 实现安全版的rawset  
  176. proxy.rawset = function(t, k, v)  
  177.   
  178.     -- 获得真实的metatable  
  179.     local mt = old_env.debug.getmetatable(t)  
  180.   
  181.     -- 如果是安全table则使用代理table  
  182.     if mt and mt[SAFE_TABLE_FLAG] then  
  183.         t = mt.__index  
  184.     end  
  185.   
  186.     -- 调用原始rawset  
  187.     return old_env.rawset(t, k, v)  
  188.   
  189. end  
  190.   
  191. -- 这里可以自定义一些自己的内容  
  192.   
  193. -- 脚本文件装载列表  
  194. local loaded_proxy  
  195. proxy.LOADED, loaded_proxy = CreateSafeTable()  
  196.   
  197. -- 导入脚本文件  
  198. proxy.import = function(s)  
  199.   
  200.     -- 如果已经被导入则返回true  
  201.     if LOADED[s] ~= nil then  
  202.         return true  
  203.     end  
  204.   
  205.     -- 装载文件  
  206.     local f, msg = old_env.loadfile(s)  
  207.   
  208.     -- 如果装载失败,输出错误  
  209.     if not f then  
  210.         old_env.io.stderr:write(msg)  
  211.         return false  
  212.     end  
  213.   
  214.     -- 否则执行该脚本  
  215.     local r, msg = pcall(f)  
  216.   
  217.     -- 如果执行过程中出错,输出错误  
  218.     if not r then  
  219.         old_env.io.stderr:write(msg)  
  220.         return false  
  221.     end  
  222.   
  223.     -- 记录文件名到装载列表  
  224.     loaded_proxy[s] = f  
  225.   
  226.     -- 成功  
  227.     return true  
  228.   
  229. end  
  230.   
  231. -- 由于外界(这里指的是main.lua)环境已经初始化过环境了,没办法在safe.lua里直接更改(我没找到办法)  
  232. -- 因此这里返回新环境给main.lua,main.lua需要在装载完该文件后把自己的环境设为该新环境  
  233. -- 对于C这一步是不需要的,本身main.lua做作的一切可以都在C里完成  
  234. do return _ENV end  
  235.    


入口脚本main.lua:

[plain]  view plain  copy
  1. -- 开启全局保护,并且更新自己的环境(见safe.lua末尾的说明)  
  2. _ENV = dofile("safe.lua")  
  3.   
  4. -- 装载其他脚本  
  5. import("test.lua")  
  6.   
  7. -- 输出已装载脚本  
  8. for k, v in pairs(LOADED) do  
  9.     print("["..k.."] = "..tostring(v))  
  10. end  
  11.   
  12. -- 尝试重复装载脚本  
  13. import("test.lua")  
  14.    

测试脚本test.lua:

[plain]  view plain  copy
  1. -- 尝试定义全局变量  
  2. x = 1  
  3. print(x)  
  4.   
  5. -- 尝试修改已有全局变量  
  6. print = nil  
  7. print(print)  
  8.   
  9. -- 尝试修改安全table  
  10. math.x = 0  
  11. print(math.x)  
  12. math.sin = nil  
  13. print(math.sin)  
  14.    


命令行里敲入lua main.lua,执行结果将为:

[plain]  view plain  copy
  1. cannot modify global enviroment!  
  2. nil  
  3. cannot modify global enviroment!  
  4. function: 6D793C3C  
  5. cannot modify safe table!  
  6. nil  
  7. cannot modify safe table!  
  8. function: 6D796C34  
  9. [test.lua] = function: 003E8310  

可以看出所有写操作都没有成功,并且test.lua只加载了一次,在LOADED中有其记录。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值