Rubyにはオブジェクトを汚染する仕組みがあった

はじめに

Ruby 3.0 Advent Calendar 2020 5日目の記事です。

昨日は、【Ruby 3.0 Advent Calendar 2020】Ruby3.0で非推奨から廃止になるメソッドたち【4日目】 - ゲームリンクスの徒然なる日常 です。

また、この記事は2020年ふりかえりアドベントカレンダー 5日目です。昨日の記事は 初学者が Ruby on Rails の広大さに途方にくれたけどなんとかやっていけるようになった話 - いまブログ です。

Ruby 3.0 から $SAFE が普通のグローバル変数になります

The feature of $SAFE was completely removed; now it is a normal global variable.

The feature of $SAFE was completely removed; now it is a normal global variable. ruby/NEWS.md at v3_0_0_preview1 · ruby/ruby

ということで、 $SAFE という特殊変数が普通のグローバル変数になります。ところで $SAFE とはなんでしょうか?これは Ruby のオブジェクトを汚染するセキュリティの仕組みに関わっていた特殊変数でした。

Ruby のオブジェクト汚染の仕組みについてざっくり説明し、$SAFE がなんだったのか、なぜ取り除かれるのか、というところをまとめてみます。

オブジェクトを汚染する仕組み

Ruby にはオブジェクト汚染という仕組みがありました。これは、オブジェクトの汚染とセーフレベルという仕組みで「外部から与えられた信頼できないオブジェクトを安全に取り扱う」こと、「信用しているオブジェクトを信用できないプログラムから守る」ことを目的としていました。CGI 時代に入力フォームから受け取ったデータを信頼しないようにしたい、という使われ方をしていたようです。

ちなみに、汚染という考え方は Perl 由来の機能です。Taint checking - Wikipedia

オブジェクトを汚染する

オブジェクト自体に汚染フラグを持つ仕組みです。汚染に関連するメソッドはこのようなものがありました。

  • Object#taint オブジェクトを汚染する
  • Object#tainted? オブジェクトが汚染されている場合に真を返す
  • Object#untaint オブジェクトの汚染を取り除く

これらのメソッドは Ruby 2.7 より非推奨となりました。 taint untaintself を返す挙動に、 tainted? は常に false を返すようになっています。 似たような trust untrusted? untrust メソッドもありますが、こちらも非推奨です。

これらのメソッドは Ruby 3.2 で削除される予定です。

セーフモデル

信用できないデータで危険な操作( eval やファイル操作、外部コマンド実行など) を行えないようにするための仕組みです。セーフレベルはレベル0〜レベル4まであり、レベル4ではプログラムを自由に終了することもできませんでした。セーフレベルはグローバル変数 $SAFE で設定することができました。

なぜこの仕組みは消えることになったか

汚染という仕組みには、

  • Ruby と関係するすべての C コードの汚染レベルとセーフレベルをチェックする必要があり、メンテナンスコストも嵩む、パフォーマンスも悪くなる
  • セキュリティを担保するレベルが粗い。汚染の仕組みを使ってセキュアなプログラムにしようと思うと多くのプログラムが動かない
  • 汚染を適切に運用していないオブジェクトがあるとセキュリティを担保できない

といった問題がありました。

また、 セーフレベル4は安全な sandbox 環境のために使えると思われていましたが、実際には脆弱性がありました。

汚染の仕組みは CGI で活用されていましたが、Ruby on Rails の台頭により CGI の必要性が下がったという理由もあったようです。

今までの流れとこれから

Ruby 2.1 でセーフレベル4が廃止されました。ruby/NEWS at v2_1_0 · ruby/ruby

Ruby 2.3 でセーフレベル2, 3が廃止されました。ruby/NEWS at ruby_2_3 · ruby/ruby

残ったセーフレベルは0(すべて信頼する)1(汚染されたオブジェクトは evalやファイル操作、外部コマンドの実行を禁止する)でした。

Ruby 2.7 を前に、汚染の仕組みを完全に廃止してはどうかというチケットが立てられました。Feature #16131: Remove $SAFE, taint and trust - Ruby master - Ruby Issue Tracking System

廃止の理由は

  • この仕組みを使っている人がほとんどおらず、ライブラリもサポートしていないこと
  • 脆弱性があり、メンテナンスコストがかかっていること

です。

このチケットで議論が行われ、

  • Ruby 2.7で 汚染の仕組みを使えないようにする。 $SAFEtaint 系のメソッドで警告を出す
  • Ruby 3.0 で $SAFE をふつうの変数にする
  • Ruby 3.2 で taint 系のメソッドを削除する

ということが決まり、削除されることになりました。

参考資料