在Lua 5.2中保护全局环境

来源:互联网 发布:热敏打印机蓝牙软件 编辑:程序博客网 时间:2024/06/02 07:30

转自: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中有其记录。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 用祛斑霜脸一直蜕皮怎么办 吃热的就流鼻涕怎么办 键盘qaz失灵其他都没事怎么办 时时彩代理抓了怎么办 6p升级ios11卡顿怎么办 微信弄没了又换号了怎么办 快手账号异常请去激活怎么办 九游账号被转移怎么办 绝地求生刺激战场闪退怎么办 qq回执编号忘了怎么办 电脑开机要用户名和密码怎么办 电脑忘记用户名和密码怎么办 微信账号密码忘了怎么办 开发者账号密保忘记怎么办 华为账号忘记密保问题怎么办 fiyme账号忘记密保怎么办 id忘了密保问题怎么办 vivo账号密码忘记了怎么办 步步高账号密码忘了怎么办 步步高手机账号密码忘了怎么办 康佳电视通行证忘了怎么办 尚游通行证忘了怎么办 深圳免限行通行证忘了截图怎么办 电脑把管理员账号删除了怎么办 uc新浪加载失败怎么办红包还 微信忘记账号和密码怎么办 苹果手机忘记id密码怎么办 购买游戏账号被找回怎么办 交易猫账号忘了怎么办 爱奇艺账号怎么修改不了密码怎么办 论文目录显示错误未定义书签怎么办 银行账号被冻结了怎么办 哈罗单车账号被冻结怎么办 麻袋赚赚账号被冻结怎么办 网赌账号被冻结怎么办 梦想城镇账号被冻结怎么办 钱被银行冻结了怎么办 百度云账号密码忘了怎么办 微信钱包忘记密码了怎么办 word文档打开文件出错怎么办 有盘文件删不了怎么办