QA@IT

$SAFE=0なのにSecurityErrorが出る条件とは?

2051 PV

以下のようなコードがあります。

def tables
  Rails.application.eager_load! if Rails.env.development?
  @models = ActiveRecord::Base.descendants.map(&:base_class).uniq.sort_by(&:name)
end

このコードが、以下に説明する処理を実行したあと、SecurityError: calling insecure method: nameを投げるようになります。このSecurityErrorをrescueして$SAFEを確認してみると、0になっています。

その処理とは、以下のようにスレッドを生成して、そちらで$SAFE=1を設定している箇所があります。しかし、$SAFEはスレッドローカルなので、直接の因果関係はないはずです。(よね?)

class UnsafeQuery
  ...
  def run
    Thread.start do
      self.result = begin
        $SAFE = 1
        Slavery.on_slave do
          eval(text)
        end
      rescue SecurityError, Exception
        $!
      ensure
        ActiveRecord::Base.connection.close
        ActiveRecord::Base.slave_connection_holder.connection.close
      end
    end.join(10)
    ...

UnsafeQueryというのは、リクエスト単位で使い捨てのオブジェクトです。textにはUser.countなどのアドホックなクエリー文字列を入れます。それをevalしているので危険な機能ですが、このアクションは一部の管理者しか使えない機能となっています。Slaveryというのは、read-onlyなDBユーザでスレーブ側にクエリーを投げるためのツールです。

質問を整理すると、3つです。

  • $SAFE=0なのにメインスレッド上でSecurityErrorが出てしまう条件はありますか?
  • connection.closeでコネクションをプールに返却しているつもりなのですが、この使い方は間違っていますか?(ActiveRecord::Base.connection_pool.connections.sizeをモニターすると、常に2になっています)
  • connection.closeがスレッドセーフでない、グローバルな状態を書き換えてしまうような処理を実行することはありますか?

ruby 2.0.0p247/p353, Unicorn, Rails 4.0.2

追記

投稿した直後に、問題は解決しました。

スレーブへの接続を実行したときにActiveRecordの抽象クラスがdescendantsに追加されてしまっていたのが直接の原因で、

ActiveRecord::Base.descendants.select{|i| !i.abstract_class? }

のようなフィルタを追加してやることで、エラーがでなくなりました。

しかし、「$SAFE=0なのにメインスレッド上でSecurityErrorが出てしまう」というのは、影響範囲がとても広いように思え、とても不安です。引き続き、この部分についての回答をお待ちしております。

ウォッチ

この質問への回答やコメントをメールでお知らせします。