SecurityTweetあらゆるソフトウェアに脆弱性は存在し得ます。Haskellについても調べてみるといいでしょう)。いって、型を設計する段階で脆弱性を回避できるよう気をつけなければいけないことには変わりませんし、GHCが生成した実行ファイル、使用するライブラリーに絶対に脆弱性がないとは言えません。現状、Haskellはほかの著名なプログラミング言語ほど使用されていないためか、あまり脆弱性が報告されることはありません(libcなど、ほかの言語の処理系も依存しているようなライブラリーの脆弱性は別として)。今回は、そんな中でもunordered-containersというパッケージについて、ドキュメントにも書かれているためおそらく直ることがないであろう脆弱性と、その回避方法について紹介します。hashdos脆弱性自体は結構有名ですし、ドキュメントに書いてあることなので、ご存知の方には何を今更感があるかと思いますが、検索した限りこの問題について日本語で説明した記事は見当たらなかったので、ここで紹介します。tohereそもそもunordered-containersって?脆弱性の前にunordered-containersパッケージについて簡単に紹介しましょう。unordered-containersパッケージは、GHCに標準で付いているcontainersパッケージよりも高速な連想配列(HashMap型)や集合(HashSet型)を提供してくれます。10.3ではなんと970ものパッケージに依存されている、超大人気汎用パッケージです。tohereどうやって高速化しているの?HashMapという名前が示しているとおり、キーとなる値のハッシュ値を計算・利用することで高速化しています。Java言語などほかの言語によくあるHashMapとは大きく異なり、内部ではハッシュテーブルを使用していません。キーを使って値を参照するMap型:ITproでも説明しているとおり、ハッシュテーブルはミュータブルな配列を内部で使用していることから、イミュータブルなデータ構造を使用して行う関数型プログラミングとは、相性が悪いのです(STモナドやIOモナドを利用したhashtablesパッケージなどを使えば、限られた範囲内でハッシュテーブルを使うこともできます)。trieという特殊な木を使っています。Blogに詳しく書かれています。こちらのスライドはScalaでの実装の話ですが、基本的にはunordered-containersパッケージのHashMapも同じはずです。trieを使ったHashMapでは、ハッシュテーブルと同様に、キーとなる値をハッシュ関数で一旦固定長の整数に変換することで、キーが存在しているかどうかの確認を高速化しています。そのため、containersパッケージよりも高速な処理ができるのです。containersパッケージのMapではキーの存在を確認する際、キー全体を既存のキーと比較する必要があるため、特に長い文字列をキーとする場合は、処理が遅くなりがちだったのです。toherehashdos脆弱性とは?hashdos脆弱性は2011年頃RubyやPHP、Perlなど多くのプログラミング言語が影響を受けるとされた、著名な脆弱性です。ここでも簡単に仕組みを説明しましょう。trieもハッシュテーブルも、必ずキーを一旦固定長の整数に変換します。文字列など、ハッシュ関数を適用されるキーとなる値は、当然固定長の整数よりも幅広い値を取り得るので、違う文字列同士でも、同じハッシュ値をとることがあります。この、違う値であるはずのキーが同じハッシュ値をとってしまった状態を「ハッシュ値の衝突」と呼びます。trieといったハッシュ値を利用した連想配列は、(単純な)配列やリストなど、やむを得ず逐次探索が必要なデータ構造を内部で使用しなければならなくなります。hashdos脆弱性はこの性質を利用したDoS攻撃です。攻撃者は、あらかじめ対象のプログラムで使っているハッシュ関数が、「必ず同じハッシュ値」を返すキー(大抵文字列でしょう)を大量に用意して、それを対象のプログラムに入力として与えることで、簡単にDoS攻撃を仕掛けることができるのです。先ほど触れた徳丸先生の記事では、PHPのアプリケーションに対してわずか500KBのform-dataを送るだけでCPU時間を1分も消費させることができたそうですから、その威力はすさまじいものと言えるでしょう。Guideには、次のように書かれています。package.要するに、「セキュリティー上問題はあるけど、SipHashのような安全なハッシュ関数を使ったらcontainersパッケージよりも速度が出なかった。それではこのパッケージの意味がない」ということです。containersパッケージよりも高速な連想配列を作るためにunordered-containersパッケージを作ったのだから、それより遅くなっては存在価値がなくなってしまうのです。従って、ユーザーが任意にキーを入力できるようなプログラムでは、unordered-containersではなく、containersを使え、ということです。このことはunordered-containersが使用しているhashableのドキュメントにも書かれています。ある意味ノーガード戦法ですね。前節で触れたとおりですが、ユーザーが任意にキーを入力できるようなプログラムでは、unordered-containersパッケージのHashMapやHashSetではなく、containersパッケージのMapやSetを使いましょう。containersパッケージにあるMapやSetはハッシュ関数を一切使っていないので、ハッシュ値の衝突も起こらず、内部で逐次探索が必要なデータ構造を使ってもいません。hashdos攻撃に遭うことはないのです。10.3で970ものパッケージに依存されているunordered-containersです。その中にはJSONのパーサーであるaesonも含まれているので、もしかしたら現状回避するのは非常に困難なのかもしれません。次回は、この問題について試しに攻撃用のコードを書いて速度の低下をチェックして報告する話を書くかもしれません...。ライセンスの下に提供されています。Analyticsでアクセス情報を収集しています。集めた情報は統計的に処理した上で、当ウェブサイトの改善のための参考情報としてのみ使用します。