Tuesday, April 22, 2014

[perl][memo] File::Tempのバッドノウハウ

■まとめ
  • tempfile(...)が作成したテンポラリファイルは、環境によってはflockされていることがある
  • tempfile(CLEANUP => 1)は、テンポラリファイルへの参照をretainする
  • つまり、CLEANUPを指定している場合、参照カウントに頼った自動closeは機能しないので、明示的にcloseする必要がある
  • また、明示的にcloseしないとflock可能にならない場合がある

■ログ
16:23:30 <kazuho_> あれ perl って file handle への refcnt がゼロになったら自動的に close してくれますよね
16:23:43 <tokuhirom> してくれますね
16:23:48 <tokuhirom> しなきゃおかしいw
16:32:33 <kazuho_> https://gist.github.com/kazuho/11168660
16:32:37 <kazuho_> こういうことだった
16:32:53 <tokuhirom> あー。それな。
16:33:01 <tokuhirom> なんか File::Temp さんごちゃごちゃやってんすよね
16:42:37 <kazuho_> linux で perl -MFile::Temp=tempfile -e '(undef, my $fn) = tempfile(UNLINK => 1); sleep 100'
16:42:47 <kazuho_> ってやっても、テンポラリファイルが開きっぱになるなー
16:49:41 <kazuho_> _deferred_unlink って関数が $fh にぎにぎしちゃうのかー > File::Temp
16:50:50 <tokuhirom> UNLINK => 1 するとなかなか UNLINK されなくなるの、だいぶアレゲですねw
16:51:16 <kazuho_> というより、
16:51:22 <kazuho_> > # close the filehandle without checking its state
16:51:23 <kazuho_> > # in order to make real sure that this is closed
16:51:30 <kazuho_> という理由で $fh をにぎりっぱにしてるから
16:51:38 <kazuho_> refcnt 減らしても自動でcloseされない
16:52:13 <tokuhirom> なんか UNLINK => 0 してやり過ごすってのを昔見た気がした
16:52:27 <kazuho_> そしたら自動削除してくれないじゃんw
16:52:33 <kazuho_> 明示的にcloseするわ…
16:53:03 <kazuho_> Starlet で select を、テンポラリファイルの flock で囲おうとしてるんだけど
16:53:15 <kazuho_> osx だと tempfile が EXLOCK 指定する
16:53:23 <kazuho_> → UNLINK してるとファイル開いたままになる
16:53:28 <kazuho_> → ロックできない!!!
16:53:34 <kazuho_> という問題なので
16:54:01 <tokuhirom> 明示的にクローズが正解かー
16:54:07 <kazuho_> ですね
16:54:09 <kazuho_> まあ、UNLINK => 1 してるとファイル開きっぱになるの、バグだと思うけどなー
16:54:15 <kazuho_> file descriptorたりなくなるじゃん!!
16:54:52 <kazuho_> まあそういう場合は tempdir してその中に手動でファイル作ろうね、なんだろうな
16:54:53 <tokuhirom> なんかでもそこ今更変えられなさそうw
16:54:57 <kazuho_> 変えようがないでしょうね
16:54:58 <tokuhirom> a-
16:55:40 <kazuho_> あざますあざます

Friday, April 11, 2014

[メモ] Starlet 0.22のリリースに伴いThundering Herd問題を再訪した件

@takezawaさんから、PerlベースのWebアプリケーションサーバであるStarletで複数ポートをlistenできるようにするPRをいただいたのでマージしました。やったね!

で、それに伴いprefork型TCPサーバのThundering Herd問題を再訪したので、その備忘録。なお、Thundering Herd問題については、prefork サーバーと thundering herd 問題 - naoyaのはてなダイアリーや、Starman と Starlet のベンチマークと Accept Serialization - Hateburo: kazeburo hatenablogあたりを参照のこと。

まず、こんなテストスクリプトを書いた: thundering-herd.pl

こいつを書いてあるコメントのとおり実行してみることで、2種類のケースでThundering Herd問題が存在するか調べることができる。

で、こいつを使った結果、以下の結論に達した。
  • accept(2)の呼出によるThundering Herd問題だが、多くの環境で過去の問題になったと考えてよい
  • select(2)で接続を待ち受け、次にaccept(2)を呼ぶようなケースでは、依然としてThundering Herd問題が存在する(当然と言えば当然だが)
とか言ってみたけど、、linux 2.6.32とOS X 10.8.5でしかテストしてないので、補足あれば教えてください m(__)m

あと、複数のポートにbind(2)するTCPサーバを書く際に注意すべき点として、prefork型サーバでselect(2)accept(2)の呼出順序で接続を確立する際は、ソケットをnonblockingモードにしておかないと、ワーカプロセスが余っているのに接続できないといった事態が発生する可能性がある。

この点は、上記のテストスクリプトを書くまでレビューするのを忘れていたんだけど、確認したらtakezawaさんのコードではちゃんと記述されていて、ちょっと感動した。いい仕事ありがとうございました。

Heartbleed脆弱性と、その背後にあるWebアプリケーションアーキテクチャの一般的欠陥について

■Heartbleedのリスクと善後策

Heartbleedは、攻撃者が一定の条件を満たすOpenSSLが動作しているサーバの、任意位置のメモリを外部から読み出すことができてしまうという脆弱性です。具体的には、以下のようなリスクが想定されています。

  • 秘密鍵の漏洩による、偽サイトの出現(あるいは中間者攻撃)
  • 秘密鍵の漏洩により、(過去のものを含む)パケットキャプチャの解読
  • サーバの同一プロセスが行った処理に関連する(他のユーザーのパスワードやセッションキーを含む)データの漏洩

漏洩した秘密鍵を用いた攻撃には、ユーザーを偽サイトへ誘導できたり、パケットの経由点を管理しているなどの、経路上の要件が必要になります。他のユーザーのデータの漏洩については、経路上の要件は不要な一方、攻撃の実施に近いタイミングでサーバにアクセスしたユーザーのデータしか漏れない、という違いがあります。

どこまで対策を施すべきかは、攻撃を受けた可能性をどの程度と評価するか、個々の組織の判断によるところがあると思うのでコメントしませんが、以下のような対策が必要になる可能性があります。

  • 偽サイトの出現や中間者攻撃、これからのパケット解読を防ぐには、新しい秘密鍵/公開鍵ペアを使うサーバ証明書への更新と、現行のサーバ証明書のrevocation
  • 秘密鍵の漏洩によって過去のパケット解読が容易になることのないよう、Forward Secrecyを満たすようなサーバ設定
  • 他のユーザーのものを含むデータの漏洩については、セッション情報のリセットとパスワードの再設定のお願い

■Heartbleedの背景

Heartbleedのような問題は、そもそもなぜ発生するのでしょう。それは言うまでもなく、安全性の根拠をプログラムが正しく記述されることに求めているからです(つまり、プログラムにバグがあると脆弱性として発現する可能性があるから)。最小権限の原則が守られていない、と言い換えてもよいと思います。

Heartbleedにおいて他のユーザーのデータが漏洩するのは、一つのプロセスが複数のユーザーの通信を処理しているからです。ユーザーごとに(あるいは接続毎に)別個のプロセスを割り当てていれば、他のユーザーの通信内容が漏洩することは起こりえません。

秘密鍵の漏洩にしても、秘密鍵を必要とする処理(TLSのハンドシェイク中の一部処理に限られます)と、TLS Heartbeatの処理(ハンドシェイク中以外に限られます)を別個の権限の元で動作させていれば、発生しなかったと言えます(17:19修正)。

多くのOSのプロセス分離やファイルアクセス権の制御に見られるように、最小権限の原則に基づいたセキュリティは有効に機能することが知られています。

にもかかわらず、ウェブ関連のソフトウェアにおいては同原則を用いずに、安全性の根拠をプログラムにバグがない点に求めるという悪しき慣習が続いています。特に、機能別の権限分離はまだしも、アクセスユーザー別の権限分離については系統だった実施例が非常に少ないという印象をもっています。

たとえば、SQL Injectionに代表されるSQL関連の情報漏洩も、アクセス制御にRDBMSのアクセス制御機構を用いず、アプリケーションプログラム内のSQL(とそのエスケープ)が正しく記述されている点に、安全性の根拠を求めているが故に発生しているわけです注1

ウェブの黎明期においては、(小数のユーザが使用することが一般的だった)RDBMSの認証システムの要件と多数が使用するウェブの要件は異なるのでRDBMSの認証システムはウェブ向けには使用できない、というのは説得力のある言い訳でした。ですが、今やRDBMSの用途の多くがウェブアプリケーションのデータストアになっているはずです。にもかかわらず、RDBMS(あるいはその他のデータストア)のアクセス制御機構をウェブアプリケーションからのアクセス制御に使う、というのは一般的な手法になっていません。

同様の問題はウェブアプリケーションサーバにもあり、複数のユーザーのリクエストを単一のプロセスで処理する結果、情報漏洩が発生するケースが見受けられます注2

リバースプロキシ、アプリケーションサーバ、データストア、そういったウェブアプリケーションの各要素について、最小権限の原則に基づいた、アプリケーションにバグがあっても脆弱にならないようなアーキテクチャの構成技法と技術開発が求められているのではないでしょうか。


注1: 強制アクセス制御がうまく機能しないようなアクセスパターンも、もちろん存在します
注2: 卑近な例としては、ログインユーザーとして別のユーザの名前が表示されるとか

Wednesday, April 2, 2014

Announcing Unco - undo changes to files made by any command

Being sick of myself occasionally wiping off the changes made to files by running wrong commands, I have started writing a program called "Unco" (pronunciation: an-ko) - a command that records the changes to the file system, and let the users undo the changes afterwards if necessary.

Unlike existing command-level solutions like aliasing rm to trash-cli, Unco is designed to be capable of undoing changes made by any program; it hooks the library calls that affect the file system and records the changes for later undoing.

The following scenario illustrates how it can be used, in case of git.
  1. instruct the shell to record all git commands
    % alias git="unco record -- git"
    
  2. edit file under the git repository
    % vi program.c
    
  3. Oops! I have accidentally reset the changes
    % git reset --hard HEAD
    
  4. don't worry, just undo the last action
    % undo history
    index    command (*=undone)
         1   git reset --hard HEAD
    % unco undo 1
    
As described, you can apply unco for rm or make install or whatever command.

The development is still in early stages, and I would not advise anybody not capable of debugging the code by oneself to use it. But for those interested, the repo is at github.com/kazuho/unco.

For the time being, the program runs on OS X and linux (edited Apr. 3 2014)only on OS X. Hopefully the command may be polished up so that it can be turned on by default; i.e. record all commands run on the shell by default.

Monday, March 17, 2014

「技術的負債」をコントロールする定量評価手法への期待

「「技術的負債」を問いなおす」というタイトルでJAWS DAYS 2014で話してきた #jawsdays - delirious thoughtsにて、追記でコメントいただけたので、外野として好き放題言わせてください。すばらしいスライドありがとうございます&いつもすみません。

僕が興味がもつとすると、それは「技術的負債」の定量評価手法についてです。

なぜ、そういう前提を置くかと言うと、それは、たとえばKrutchenによる「技術的負債」の定性評価は、とてもわかりやすいものの、技術を取捨選択するツールとしては使えないからです。

スライドでは、技術評価における将来の不確定性を象徴する問題としてSSDの普及前夜にシャーディングをがんばって実装してしまう例をご紹介いただきましたが、実際、そのような不確実性を織り込んだ正しい決定を我々が日々のエンジニアリングで下すことができているのか疑問に感じることが多いです。思いつくがままに挙げると、例えば以下のようなことを疑問に感じたことがあります。

  • ライブラリやフレームワークを採用するにあたり、その将来性を何年程度のスパンで評価すればいいのか
  • ウェブサービスを開発するにあたり、最初からシャーディングの機能を作り込むことは正しいのか
    • それとも、サービスが当たってからシャーディング機能を実装するほうが利益は大きいのか
  • プロトタイピングにおいて、きれいな実装を行うべきなのか、それとも汚くても早期に実装できたほうがいいのか
    • プロトタイプのコードをサービスインする確率によって答えは変わるはずだが、その確率の閾値はいくらなのか

これらの例からもわかるように、ライブラリ等の部品を選定する際の評価や、規模拡大や拡張を前提とした作り込みの程度を決定するには、「技術的負債」についての定性評価ではなく定量評価が必要です。そして、kentaroさんによる「割引率」という概念の導入は、それを可能にするという点で、僕には興味深く感じられます。

感覚的に言うと、

  • 割引率は、ある期間の後にそのソフトウェアをメンテすることになる可能性
  • 期間毎の割引率に「技術的負債」の解消に必要なコストを掛けて積分したものが、予測されるTCO
  • 予測されるTCOを最小に抑えるのが、正しい技術的選択

のような形として、定量的に評価し、現時点において正しい選択を取るためのツールとして「技術的負債」を使うことが可能になるのではないか。

「これからはnode.jsでしょ」とか「サービスがヒットしたら必要になるから、シャーディング機能は作り込むべき」といった感覚論ではなく、技術的判断をサポートする定量評価手法として「技術的負債」を使えればいいなと思う次第です。


23:37追記: ↓とのことです。ありがとうございます。

Monday, March 10, 2014

拡張可能なWeb APIの設計原則と、バージョン番号を使う理由について

APIのバージョニングは限局分岐でやるのが良い - Hidden in Plain Sightにはブコメしたのですが、Rebuild: 35: You Don't Need API Version 2 (Kenn Ejima)でも本件に言及があったようなので、少し一般論を書いておきたいと思います。


■Web APIの設計原則について

そもそも、良いAPIとはどのような特性をもつものでしょうか? 一般的に、以下の2点が挙げられると思います。
  • 拡張が容易である
  • 拡張時に後方互換性を破壊しない

ウェブの場合は、これに加え、
  • スケーラブルである
  • HTTPに起因する問題に上手に対処できる
ことが求められます。

前2者はウェブに限らない要件です。これを満たす設計手法としては、
  • リクエストおよびレスポンスのパラメータを拡張可能に
  • 互換性を壊す拡張が必要な場合は、関数名を変える
    • 古い関数は従来と同じ機能を提供する
  • ステートフルAPIは、抽象データ型の使用
といった手法が一般的です。

たとえばMicrosoft WindowsのAPIを見ると、多くのリクエストとレスポンス型が拡張可能であり(cbSizeというフィールドで拡張領域を指定する)、非互換な拡張がされたAPIは「〜Ex」という名前をもっており、ステートの表現にはHANDLEという抽象データ型が使われていることが分かります。

これに対し、後2者はウェブ固有の色合いが強い要件です。そして、そのための設計手法として、
  • (出来る限り)ステートレスなAPI
  • 冪等性に対する配慮
が求められる、ということが知られています(したがって、「ステートフルなAPIにおける抽象型の利用」という一般的な設計手法は検討対象外となる)。

以上の2者をまとめると、Web APIを設計する基本原則として、以下の4点を採用するのが良い、ということになります。
  • 拡張可能なパラメータ
    • JSONのような拡張可能なシリアライズフォーマットを用い、拡張可能な形で入出力パラメータを設計する
  • 互換性を壊す必要がある場合は関数名を変更する
  • できるだけステートレス
  • 冪等性に対する配慮
    • よく分からなければ、とりあえずRESTを使う

このような設計をとっていれば、APIを拡張しても後方互換性を確保することができますし、バージョン番号による分岐も必要になりません。


■バージョン番号をURLに含めるべき理由

述べたように、拡張可能なWeb APIの設計において、バージョン番号による分岐という概念は必ずしも必要なものではありません。ですが実際は、それでもバージョン番号をURLに含めるべきだと考えられることがあります。

APIが拡張(ないし変更)されていくということは、すなわち、サーバサイドの提供するAPIの「バージョン」とクライアントアプリケーションが期待するバージョンの齟齬による互換性問題が発生しうるということを意味します。そして、そのような互換性問題は、実行中の良くわからないエラーではなく(データ保存しようとしたらエラーとか最悪ですね!)、明確にトラブルシュートできる、つまり、起動時のバージョンチェックで検出され、ユーザーや管理者に明確に通知されることが望ましいということになります。

実際には、以下のような要件のもとで、確実に機能するバージョンチェック機構が望ましいということになります。
  • クライアントが「廃止された」APIに依存しているかどうかは、サーバサイドでないとチェックできない
  • クライアントアプリケーションの開発者は、しばしば「バージョンチェックを怠る」

もうお分かりでしょうか。この要件を最も単純に実現する手法が、APIのURLにバージョン番号を含めるという手法なのです。APIに対する全てのリクエストにバージョン番号が含まれるため、「クライアント側でバージョンチェックを怠った」がゆえのエラーは発生しようがありません。また、サーバサイドのログにもバージョン番号が残るため、管理者の側で問題の切り分けを要求された場合にも、対処することが容易です。

これが、バージョン番号をAPIのURLに含めるべきと考えられる理由です。必ずしも全ての条件において必要ではないかもしれませんが、一考に値するのではないでしょうか。

なお蛇足ですが、APIのバージョニングについては、サービスとしてしか提供しないソフトウェアの場合は、後方互換性を崩す度にバージョン番号の変更が必要です。また、サーバをソフトウェア製品として提供する場合には、機能拡張を行う度にバージョン番号の変更が必要になります注1(2014/3/11加筆)。ですので、特に後者のバージョン番号発番については、semantic versioningを参考に、MAJOR.MINORのような記法を採用するのが望ましいと言えるでしょう。


参考文献: Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

注1: クライアントの機能が特定のマイナーバージョン以降に依存することもあり、かつ、サーバサイドのバージョンがクライアントサイドのそれより古いことがあり得るため

Tuesday, February 25, 2014

ウェブアプリの「合理的な」セキュリティ対策に関する一考察

※※※ミドルウェアを中心に、ウェブ関連の研究開発に携わっている者による雑文です※※※

ウェブの脆弱性は、ウェブアプリケーションのバグに起因するものと、ウェブブラウザのバグに起因するものの2者に大別することができる。

ウェブアプリケーションを開発/提供する仕事に従事している者には、この前者、すなわち、ウェブアプリケーションのバグに起因する脆弱性を最小限に抑え込むことを求められる注1

かといって、脆弱性がないことを保障するのは難しい。「ウェブアプリケーションにバグがあっても脆弱性とはならない(あるいは被害が限定される)ような設計」を採用するのが現実的だと考えられる。

OSにおける、プロセス間のメモリ分離やuserIDに基づいたファイルへのアクセス制御を考えてみると、OSがセキュリティを「強制」するため、アプリケーション側で不正なコードが実行されても脆弱性とならない、もしくは、影響を小さく抑え込むことができるようになっていることがわかる。

ウェブ技術における同様の例は数多いが、たとえばXSS脆弱性対策を見ると、


といったものを挙げることができる。また、SQL Injection対策を見ると、


等の手法が知られている。

これらの対策をひとつ選択し、あるいは組み合わせて使うことで、コーディングミスがあったとしても脆弱性が発現しない(もしくは発現する可能性が低い)アプリケーションを実現することができる

ただ、この種の技術には多かれ少なかれ、アプリケーション側のコードに不自由を強いるという側面がある。

たとえば、Content Security Policyには、インラインの<SCRIPT>タグを実行しづらいという制限がある(1.1で修正見込)し、例として挙げたSQL Injection対策のいずれもが現実的でないウェブアプリケーションも多いだろう。また、SQLにおける条件節の漏れによる情報漏洩のように、本質的に対策が難しい注3問題も存在する。

以上のように、共通モジュール(あるいは下位レイヤ)でアクセス方法を「強制」する仕組みを用いることで、脆弱性への耐性を高めるという情報工学における一般的なアプローチは、ウェブ技術においても有効であり、積極的に使用すべきである注4。一方で、述べたように、今後の発展が期待される分野も存在する注5



注1: 後者については、一義的にはウェブブラウザベンダーが対応すべき問題である。もちろん、ウェブアプリケーション側で緩和策が実装できるならすれば良いケースもある

注2: 最新のテンプレートエンジン事情を良く知らないので列挙はしません。また、DOM APIベースのアプローチについても本稿では割愛します。

注3: ウェブアプリケーションにおいては、アクセス制限とアクセスを単一のクエリで記述することを求められることが多いため。この点は、ケーパビリティの概念を導入したORMのようなアプローチで解決可能なのかもしれないが…

注4: 「IPA 独立行政法人 情報処理推進機構:安全なウェブサイトの作り方」では、脆弱性を9種類に類型化して説明しているが、そのほとんどは「アプリケーションプログラマがミスをしても問題ない」ような共通コード(ウェブアプリケーションフレームワークやライブラリ等)の使用により回避することが可能であるし、そのような実装が称揚されるべきである

注5: なので、研究課題として面白いと思います