2007年 09月 26日

環境に一切干渉できない eval

#!ruby -Ku

# クラス化して Module.new.instance_eval するようにしてみる。
# まだなんかあるかなぁ……

class SafeEval

	def safe_eval(code, tm=1)
		result = nil
		tg = nil
		th = Thread.start do
			# スレッドグループを作り、
			# 新たなスレッドはすべてこれに所属させる。
			tg = ThreadGroup.new.add(Thread.current)
			$SAFE  = 4
			result = Module.new.instance_eval(code)
		end.join(tm)
		# 生成されたスレッドをすべて削除
		tg.list.each {|t| t.kill }
		raise TimeoutError unless th # タイムアウトした場合 Thread は nil を返す
		result
	end

	alias eval safe_eval

end

if $0 == __FILE__
	require "test/unit"
	class SafeEvalTest < Test::Unit::TestCase

		def setup
			@t = SafeEval.new.taint
		end

		def test_safe
			assert_raise(SecurityError) do
				@t.safe_eval("puts ''")
			end

			assert_raise(SecurityError) do
				@t.safe_eval("$foo = :foo")
			end

			assert_nothing_raised(SecurityError) do
				@t.safe_eval("def hoge; end")
			end

			@t.safe_eval <<-EOS
				def safe_eval(code)
					"Nice boat."
				end
			EOS
			assert_not_equal("Nice boat", @t.safe_eval("nil"))

		end

		def test_safe_access
			assert_raise(NoMethodError) do
				@t.safe_eval("@foo << :bar")
			end
			assert_equal(nil, @t.instance_variable_get(:@foo))

			assert_raise(NameError) do
				@t.safe_eval("@@foo")
			end
		end

	end
end

まだなんかあるかなぁ……
SafeEval クラスは毎回つかいすてる。
インスタンス変数は無名 Module でさよならする。
クラス変数は SafeEval クラスが untaint である限りつくれない。