1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • EEF CNA が公開したCVEの35.8%は、制御されていないリソース消費であり、BEAMエコシステムでは繰り返し発生するatom枯渇が大きな割合を占めている
  • Atom枯渇 はサービス拒否脆弱性であり、atomはガベージコレクションされずグローバルテーブルに蓄積され、テーブルがいっぱいになるとVMがクラッシュする
  • ユーザー入力 のように、取り得る値の集合が有限だと保証されないデータからatomを作ると、DoSの危険が生じる。URI schemeも例外ではない
  • 危険は binary_to_atom/1String.to_atom/1 のような明示的な呼び出しだけでなく、JSONキーのatomデコード や文字列補間ベースの動的生成にも存在する
  • 安全に扱うには、実行時の新規atom生成を避け、既知の値は 明示的なルックアップテーブルto_existing_atom 系に制限し、リンターで点検すべきである

Atom枯渇が生み出すサービス拒否脆弱性

  • EEF CNA が公開したCVEのうち 35.8% は制御されていないリソース消費であり、BEAMエコシステムでは繰り返し起きるatom枯渇問題が大きな割合を占めている {p:36}
  • 現在の分布はEEF CNAの Common Weaknesses ページで確認できる
  • Atom枯渇 はサービス拒否(DoS)脆弱性である
    • Atomはガベージコレクションされない
    • グローバルatomテーブルに保存される
    • テーブルがいっぱいになるとVMがクラッシュする
  • 有限ではない値、特に ユーザー入力 からatomを作ると、潜在的なDoSになる
  • 危険は明白な呼び出しだけに限られない
    • Erlangの binary_to_atom/1list_to_atom/1
    • Elixirの String.to_atom/1List.to_atom/1
  • 目立ちにくい危険なパターンも存在する
    • Erlangで補間によって動的にatomを生成する例:
      % Erlang: 보간을 통한 동적 atom 생성
      list_to_atom("field_" ++ UserInput)
      
    • ElixirでJSONキーをatomとしてデコードする例:
      
      
      
      # Elixir: JSON을 atom 키로 디코딩
      Jason.decode(json, keys: :atoms)
      
    • Elixirで補間によって動的にatomを生成する例:
      
      
      
      # Elixir: 보간을 통한 동적 atom 생성
      :"field_#{user_input}"
      

安全な処理方法と点検対象

  • Atom枯渇脆弱性は単なる不注意ではなく、入力が制御されている、または有限だと仮定したコードでよく発生する
  • URI scheme は代表的な例である
    • 処理するschemeは数個しかないように感じられることがある
    • 値が外部入力から来る場合、取り得る集合がもはや有限だと保証できない
  • 入力からatomを作るコードは、取り得る値の集合が 有限で、既知であり、強制されている場合 でなければ安全ではない
  • 最も安全なアプローチは、実行時に新しいatomを作らないことである
  • 許可される値が既知なら、明示的なルックアップテーブル を使う方が安全である
    % Erlang
    case Scheme of
        <<"http">> -> http;
        <<"https">> -> https;
        _ -> error
    end
    
  • ルックアップテーブルが実用的でない場合は、新しいatomを作らず既存のatomだけを使う変種を使うべきである
    • これらの関数は新しいatomを生成せず、エラーを発生させる
    % Erlang
    binary_to_existing_atom(Value)
    list_to_existing_atom(Value)
    
    
    
    
    # Elixir
    String.to_existing_atom(value)
    List.to_existing_atom(value)
    
  • リンターは、脆弱性につながる前に危険なパターンを見つけるのに役立つ
    • ElixirプロジェクトではCredoの Credo.Check.Warning.UnsafeToAtom を有効化することを検討できる
    • このチェックは String.to_atom/1List.to_atom/1Module.concat/1,2keys: :atoms を使う Jason.decode/2 の安全でない呼び出しを指摘する
    • このチェックはデフォルトでは無効になっている
  • ErlangまたはElixirプロジェクトのメンテナーは、バイナリ、文字列、JSONキー、URIコンポーネント、ヘッダー、設定値からatomを生成するコードを検索すべきである
  • この脆弱性カテゴリは、CVEになる前に修正しやすいタイプの1つである
  • さらに詳しいガイダンスは、EEF Security Working Groupの atom枯渇防止ガイド にまとめられている

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rs の意見
  • Ruby で Symbolガベージコレクション対象になる前の状況に似ているように聞こえる

  • タイトルの意味がわからない。これは明らかに footgun に見える

    • タイトルの趣旨は、atom 枯渇を「単なる footgun」と呼ぶと問題の深刻さを過小評価する、ということのようだ
    • 記憶が正しければ、Erlang を毎日使っているわけではないが、atom はガベージコレクションされない
      「Ruby にも Erlang の atom のような symbol があるのでは?」と思うなら、その理解自体は合っているが、Ruby は symbol をガベージコレクションする
      しかも、デフォルトでは Erlang の atom が格納されるルックアップテーブルは最大 1,048,576 個までしか許容しない
      フォームのようなユーザー入力から atom を動的に生成すると非常に危険で、ソフトウェアはサービス拒否攻撃にさらされる
    • 「単なる」footgun よりも大きな問題だ、という意味だと理解した
      ただ私の経験では、「footgun」自体がかなり広い表現なので、どちらにしてもこのタイトルの言い回しは不自然だ
    • そう、それもとてつもなく大きな footgun に見える
  • 何か根本的な部分の 設計や実装が悪い ように聞こえるので驚いた。ネット上でずっと称賛されてきた言語だけに、なおさら意外だ

    • BEAM の atom は本質的に インターンされた文字列で、グローバルなバイト列↔整数テーブルを持つ
      そのテーブルに参照カウントを追加するとコストが高く、何十年も存在してきたコードのスケーリング特性が変わってしまう
      atom の最大数はデフォルトで 100 万個で、VM 起動時に決まる
      落とし穴ではあるが、避けるのが難しいわけではない。かなり昔からの推奨事項は「ユーザー入力から atom を作るな」だった
      たとえば JSON をパースするなら、通常はキーを atom に変換しないか、既存の atom である場合にだけ変換する。そうすれば atom キーでパターンマッチでき、コードロード時にそれらの atom はすでに作られており、包括句は atom の代わりに文字列を受け取れる
    • Erlang は、特殊な用途で専門プログラマーが使う ニッチな言語だった点を考慮すべきだ
      Elixir ははるかに一般的なので、Erlang 開発者はこれを知っている可能性が高いが、Elixir 開発者は知らない可能性がある
  • 個人的には、atom をそういう形で使うこと自体が変に感じられる。Erlang の atom をだいたい C の enum 型に近いものとして理解しているからだ
    あるやり方で単語を入力すると、内部的に enum になる便利機能、というイメージだ
    記事ではユーザー入力に触れているが、そもそもユーザー入力から新しい enum 型を作りたくなるユースケースがなぜあるのかわからない。用途はきわめて限定的に見える
    横のコメントではパースの話をしているが、理想的には事前にわかっているデータ構造をパースするものではないのか? 何か見落としている気がする

    • 質問からは少し外れるが、K では symbol がグローバルにインターンされており、Erlang のように K プロセスの symbol テーブルを枯渇させて落とすことができる
      その言語で symbol を単なる高速な等価比較以上の別型として持つ利点は、少なくとも 2 つある。symbol は atom、つまり文字のリスト状シーケンスではなく原子的な単位なので、複数の演算子が異なる扱いをする。また symbol は ベクトル化されており、単一型リスト内に高密度に格納できる
      K と Q では、データベーステーブルの列をベクトル化された型で表現するのが非常に望ましい。局所性が良く、メモリ効率が高く、多くの演算子に高速経路があるからだ。ただし symbol テーブルの制約があるため、カーディナリティの高い列に symbol を使うときは注意が必要だ
      既知のスキーマの JSON をパースするなら、symbol は辞書キーとして非常に優秀で、k2/k3 では事実上必須だ。しかし正体不明の JSON であれば、ユーザー入力から来ていてはいけない
      一部の K 方言では symbol の長さを短く制限し、64 ビット値に詰めて運べるようにしている。汎用性を犠牲にする代わりに、symbol テーブル自体が不要になる
  • 「制御された入力」と「制御されていない入力」の区別は、セキュリティでは null かどうかのようなものに感じる
    webpack-plugin-less-css が信頼できない CSS ファイルを受け取るとサービス拒否になる、のような項目を見ると CVE 疲れをかなり感じる
    それでも、ここでもう少し良い境界の示し方があるとよいと思う。たとえば文字列連結を経たときに、どの安全性の性質が保存されるのかといった合成規則も、うまく扱えるとよい
    そして HTTP POST で受け取ったものを大量に SafeString 扱いにしていたなら、それはある程度は本人の責任だ