论坛首页 编程语言技术论坛

做一个彻底干净的sandbox

浏览 1940 次
精华帖 (0) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-02-18   最后修改:2009-02-18

有时候,我们想要提供一个清洁的环境运行一些不太可信任的代码:
譬如给用户做个计算器,或者做一个像ruby-lang上面的ruby.new那么酷的控制台。
于是就要建立一个sandbox。

典型的做法是这样:

Thread.new do
  $SAFE = 4
  eval some_string
end



safe level = 4时,可以对运行的代码进行很多限制。
譬如新创建的对象都视为tainted,不能修改文件等等……

我们也可以用Thread#kill或者Thread#kill!终止它

但……这样是否足够了呢?
如果我们想屏蔽某些类,让用户看不到它呢?——还是做不到。
而且,有不调用untaint而使对象untaint的方法!(不要问我,我也不知道!)


我能想到的几种解决方法:

a. 像http://github.com/why/sandbox/tree/master那样,给ruby解析器打补丁,重新编译,
然后就可以用Sandbox.new,Sandbox.eval……了

b. 用racc做一个parser,解析字符串成语法树然后运行。
我承认,通过做一个四则运算解析器学习racc是一件愉快的事情……
但是当你的语法比四则运算更复杂时,就不好对付了。

c. 是下面要介绍的:新建一个进程,在新进程里eval~~ (以windows为例)

 

-----------------------------------------------------------------------------------------------------------------------------------------------


1. 如果eval很多,每次都建立一个新进程的话耗不起,所以先启动一个DRb server。
server内容很简单,作为例子,这里设置只允许本机访问。
btw:下划线纯属恶趣味,写___123456789___也一样

#drbsvr.rb
require 'drb'
require "drb/acl"

module EvalServer
  class << self
    def __eval__ str
      begin
        res = eval str
	@__err__ = nil
	return res
      rescue Exception => ex
	@__err__ = ex.message
        nil
      end
    end
    def __get_error__
      @__err__
    end
  end
end

#deny all other connection except local call
__acl__ = ACL.new( %w[ deny all
                   allow localhost] )
DRb.install_acl(__acl__)
DRb.start_service("druby://:14194", EvalServer)
DRb.thread.join



2. 在windows建立后台进程。

不能用fork,系统不支持。

也不能用system()——那样会产生一个cmd窗口 (那就不是"后台进程"了)。
要建立后台进程,需要用Win32API或者DL把CreateProcess导出来。

以下为一部分代码

#...
#stdin, stdout, stderror are handles (Fixnum)
def create_process(command, stdin, stdout, stderror)
      params = [
    'L', # IN LPCSTR lpApplicationName
    'P', # IN LPSTR lpCommandLine
    'L', # IN LPSECURITY_ATTRIBUTES lpProcessAttributes
    'L', # IN LPSECURITY_ATTRIBUTES lpThreadAttributes
    'L', # IN BOOL bInheritHandles
    'L', # IN DWORD dwCreationFlags
    'L', # IN LPVOID lpEnvironment
    'L', # IN LPCSTR lpCurrentDirectory
    'P', # IN LPSTARTUPINFOA lpStartupInfo
    'P']  # OUT LPPROCESS_INFORMATION lpProcessInformation

    startupInfo = [STARTUP_INFO_SIZE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW, 0,
      0, 0, stdin, stdout, stderror].pack('IIIIIIIIIIIISSIIII')

    processInfo = [0, 0, 0, 0].pack('IIII')
    command << 0

    createProcess = Win32API.new("kernel32", "CreateProcess", params, 'I')
    raise_last_win_32_error if createProcess.call(0,
     command, 0, 0, 1, 0, 0, 0, startupInfo, processInfo).zero?

    hProcess, hThread, dwProcessId, dwThreadId = processInfo.unpack('LLLL')

    close_handle(hProcess)
    close_handle(hThread)

    [dwProcessId, dwThreadId]
end


当然……看完这段你可能会不介意用system()弹出一个cmd窗口了。

 

3.启动server并连接。
btw:这里如果不新建一个线程,server进程会替代原来的进程,程序就运行不下去了

此处start_service容易引人误解,其实只是连接到server的准备工作而已。

Thread.new do
  create_process('ruby drbsvr.rb', 0, 0, 0)
end

DRb.start_service 
eval_svr = DRbObject.new(nil, "druby://localhost:14194")



4.现在eval_svr就是我们取得的EvalServer对象了。fμck time。

eval_svr.__eval__ "little f*ck"
puts eval_svr.__get_error__



我还尝试过让eval server返回一个binding,但是eval时会检查类型。
保存一个清洁的binding然后读出来?binding不可序列化。

个人的小小研究,欢迎大家指教~~

 

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics