6月172013
0
たぶん大丈夫だと思うんだけどヤバかったら教えて下さい。
(皆様からの温かいトマホーク(2)(3)によるとやっぱダメなようです)
要件
– ブラウザでRubyのコード書かせて、サーバーに保存してサーバーで実行したい– 危険な事はされたくない。ファイルへのアクセスやコマンドの実行、やたら時間のかかる処理など
– 安全に実行できたらコードの返り値を取得したい。コードが危険だったらエラーを取得したい。
– 危険な事されても、コード実行しているプロセスは終了しないでエラーをブラウザに返したい。
– コードはWebサーバーと同じプロセスで実行したい
調査
ということで調べていたらsafelevelを使えばいいらしい– Programming Ruby: The Pragmatic Programmer's Guide
– Rubyのセーフレベル4環境とその使い方 – ¬¬日常日記
$SAFEに0〜4までの数値を入れるとセーフレベルが変化する。Ruby起動時は0。
4(最高)の時はファイル操作やコマンド実行、標準出力もできなくなる。
一度上げたセーフレベルは下げれないが、Threadを作ってその中でセーフレベルを上げればThread外はセーフレベル0のままになるのと、
セーフレベル0の時にProcオブジェクトを作ればセーフレベル0環境をThread内に持ってこれるのを利用すれば、要件は満たせそうだ。
できた
このSandBoxクラスを使えば安全に実行できる。SandBox.eval(コード, コールバック)で実行する。
コールバックでコードの実行結果か例外オブジェクトが返ってくる。
sandbox.rb
require 'timeout'
class SandBox
def self.eval(code)
throw ArgumentError, "callback not given" unless block_given?
result_or_error = nil
begin
Timeout::timeout 1 do
result_or_error = Thread.new do
$SAFE = 4
instance_eval code
end.value
end
rescue StandardError, SecurityError, Timeout::Error => e
result_or_error = e
end
yield result_or_error
end
end
## 安全なコードを実行
goodcode = File.open("goodcode.rb").read
SandBox.eval goodcode do |res|
puts "-- goodcode.rb --"
p res
end
## 危険なコードを実行
badcode = File.open("badcode.rb").read
SandBox.eval badcode do |res|
puts "-- badcode.rb --"
p res
end
安全なコード(goodcode.rb)と危険なコード(badcode.rb)を実行してみた。
goodcode.rb – ちょっと計算して値を返しているだけなので安全
"cool!" * 10
gadcode.rb – ファイルアクセスや標準出力をしていて危険
File.delete "./hoge" if File.exists? "./hoge" # ファイル削除
puts "this is bad code" # 標準出力
system "ls" # コマンド実行
1+2*3/4 # ふつうの計算
sandbox.rb の実行結果
-- goodcode.rb --File.exists? の時点でエラーがでていた。もちろんputsもsystemもSecurityErrorになる。
"cool!cool!cool!cool!cool!cool!cool!cool!cool!cool!"
-- badcode.rb --
#<SecurityError: Insecure operation `eval' at level 4>
無限ループとか書かれても1秒でタイムアウトする。
このセーフレベル4環境を破るには、セーフレベル0を持っているコールバック関数を呼び出す必要があるのだが
ちょっと試した感じでは無理っぽいのでまあまあ安全だと思う。
ヤバイ処理を実行させられそうになったら即例外をコールバックするので、アプリも落ちないし俺の要求を満たしてる。
物凄いサイズのHash作られたりしたらダメかも。