Impact: An attacker with a privileged network position may capture or modify data in sessions protected by SSL/TLS
Description: Secure Transport failed to validate the authenticity of the connection. This issue was addressed by restoring missing validation steps.
つまり、このバグは中間者攻撃(man-in-the-middle attack)を引き起こし、バグの説明には、この問題は接続認証の正当性チェックをミスするステップに起因すると書かれています。
ここでもう一つ、SSL/TSLに関連するバグやセキュリティ問題がインターネット上で発生した場合、ユーザであろうとプログラマであろうと、その問題を非常に重視しなければなりません。このネットワーク通信の暗号化プロトコルは、セキュリティが最も必要とされる多くの場所で広く使われているため、SSL/TSLに問題が発生すれば、それは世界のコンピュータセキュリティシステムの崩壊を意味します。
Bugコードの理由
Adam Langley氏のAppleSSL/TLSのバグ 投稿で、バグの詳細が明らかになりました。
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
OSStatus err;
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
err = sslRawVerify(ctx,
ctx->peerPubKey,
dataToSign, /* plaintext */
dataToSignLen, /* plaintext length */
signature,
signatureLen);
if(err) {
sslErrorLog("SSLDecodeSignedServerKeyExchange: sslRawVerify "
"returned %d
", (int)err);
goto fail;
}
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
if文には中括弧がないので、最初のgotoだけがifに属し、2番目のgotoは常に実行されます。つまり、前のifチェックがすべて失敗しても、goto fail;は実行されます。failタグ内のメモリを解放した後、return err;が表示されます。
SSLHashSHA1.update()が成功、つまり0を返したときに何が起こるか考えてみましょう。そう、本当の仕事をする sslRawVerify() はバイパスされるのです。そして関数SSLVerifySignedServerKeyExchange()も0を返し、これは成功を意味します!ニマ!おそらくCoolShell.comの記事"A Space Causes a Fiasco"を思い浮かべていることでしょう。すべて低レベルのバグです。
この低レベルのバグはこの週末、インターネット上で話題になっており、Twiterで#gotofailのハッシュタグの祭典をチェックすることができます。
XKCDが好きなら、間違いなくこの漫画を思い浮かべるでしょう:
注:このバグはTSLバージョン1.2には影響しません。バージョン1.2ではこの関数を使用せず、別のメカニズムを経由するからです。しかし、クライアント側でバージョンを選択できることを忘れないでください。
お使いのブラウザで問題が発生するかどうかをテストしたい場合は、同日公開されたhttps://.om 。
いくつかの考え
以下は、このテーマについての私の考えです。
)コンパイルアラームについて
アップル社のコードにあるgoto文はデッドコード、つまり決して実行されることのないコードを作り出し、C/C++プログラマーは警告を受けるだろうと言う人がいます。しかし、実際、死んだコードはデフォルトでは警告されません。たとえ-Wallを追加しても、GCC 4.8.2もClang 3.3も、デフォルトの警告レベルのVisual Studio 2012も含めて、警告を発しません。gccとClangには、次のパラメータがあります: もちろん、他の静的コード・チェック・ツールでも、この低レベルの問題をチェックできるものがあります。
また、IDEのコード自動書式設定ツールを使うことで、少しは解決できるのではないでしょうか?少なくとも、そのインデントを一見間違って見えるようなものに変えてくれるかもしれません。
)コードマージとコードレビューについて
このバグの差分を見るには、こちらのコードを比較するか、https://.comアクセスしてください。
diff -urN <(curl -s http://..com/source/Security/Security-.13/libsecurity_ssl/lib/.c\?txt) \ <(curl -s http://..com/source/Security/Security-55471/libsecurity_ssl/lib/.c\?txt) \コードdiffを見ると、Appleがコードをリファクタリングしていることがわかります。
つまり、2つのgoto fail文は、おそらく異なるブランチ上のコードに対してマージを行ったために起こったのだと推測できます。これは、バージョン管理ツールを使ってコードをマージするときによくある問題です。コードにたくさんの差分がある場合、この問題を見逃しがちです。コードレビューがあったとしても、この問題のあるコードを見つけるのは困難です。次の diff を見て、このエラーに気づくでしょうか?
つまり、リファクタリングブランチのコードは正しいのですが、ブランチのマージ中にマージツールによってめちゃくちゃにされてしまうのです。ですから、コードマージをするときには注意深く慎重を期す必要があり、マージツールを完全に信用してはいけないのです。
)テストについて
明らかに、このバグをコードレビューで見つけるのは非常に困難です。コードのリファクタリングや、コードマージでの多くの差分では、レビューされにくいのです。
もちろん、後知恵のある人たちが「テストによって問題を発見できたはずだ」と言うのは簡単ですが、本当にそうでしょうか?
なぜなら、この関数はネットワーク・ハンドシェイクの奥深くにあり、機能テストではとてもカバーできないからです。このようなケースを書きたい場合は、TSLスタックに精通している必要があります。非常に複雑なことです。全てのケースを書くのはとてもとても大変なことです。SSLVerifySignedServerKeyExchange() 関数の詳細については、関連するServerKeyExchange RFC ドキュメントを参照してください。
問題だけを見れば、この関数の単体テストで問題が明らかになると言うでしょう。しかし、SSL/TSLは何年も前から存在しているので、基礎となる関数はかなり安定しているはずです。
十分な時間があれば、すべてのファンクションポイント、すべてのファンクションでUTを行い、コードカバレッジとブランチカバレッジを同じように探求することは可能です。しかし、絶対にできないことがあります。それは、ネガティブなケースをすべて洗い出すことです。ですから、テストでは極端なことはできませんし、より賢くテストする必要があります。私の記事ServerKeyExchange」で述べたように、テストはコーディングよりもはるかに難しく、テストは上級開発者でなければうまくできない仕事です。私は、コードを書かない人がテストをうまくできるとは決して思いません。
ここで私が言いたいのは、テストによって問題が発見される可能性が低いということではなく、テストは重要であり、単一テストはさらに重要であるということです。しかし、すべてをカバーすることは不可能です。注意が払われていないところでは、必ず愚かなミスが起こります。
追伸:この件に関する大手サイトでの議論では、OS X の cul コマンドは、認証されていない IP アドレスからの https リクエストを受け付けることができることが確認されています。
)コーディングスタイルについて
もしブロック中括弧を強制的に使用すれば、両方のgoto失敗がifブロックの中に入ることになり、メンテナンスも読みやすさも向上します。もしブロック中括弧の使用を強制すれば、両方のgotoフェイルが1つのifブロックの中に入ることになり、これも保守しやすく読みやすくなります。
また、ステートメントブロック括弧を使用せず、単一のステートメントのみを使用することに固執する場合は、1つのステートメントであり、同じ行に配置する必要があるとも言われています。以下のように:
if (check_something) do_something();
しかし、シングル・ステップでコードをデバッグしているときに、STEP OVERしたときにif条件がtrueかfalseかわからなくなってしまうのはちょっと残念です。そこで、複数行に分割して中括弧を追加するのがよいでしょう。
同じような問題は、10年以上前にもありました。その時はもっと大きな問題があり、ユーザーに間違ったデータを提供することになりました。その時は他の人のコードをメンテナンスしていたのですが、他の人のコードはAppleのコードのようにif文のブロック括弧がありませんでした。私はreturn zで関数を呼び出そうとしたのですが、それはお茶の子さいさいでした:
if ( ...... )
return x;
if ( ...... )
return y;
if ( ...... )
foo()
return z;
人間の脳は当たり前のように、インデントはすべて1つのステートメント・ブロックの中にあると思い込んでしまうので、このようなミスはうっかり犯してしまうのです。しかし、もし元のコードに中括弧が追加され、その後にインデントが普通に行われていたとしたら、後でそのコードをメンテナンスする人々にとっては非常に良いことだったでしょう。私が犯したような安直なミスは犯さなかったでしょう。下のコードのように、書くとちょっと言葉足らずですが、人のためになります。
if ( ...... ){
return x;
}
if ( ...... ){
return y;
}
if ( ...... ){
return z;
}
これと似たようなコードスタイルは以下の通りですが、どちらが読みやすいと思いますか?
- if 和 if
- if 和 if
- if そしてもし
- if ) and if == true )
だから、コードはクールなところではクールではなく、他の人が読むためのものなのです。
また、このアップルのコードはなぜ以下のような形で書かれていないのでしょうか?ほら、この場合は以下のようにすっきりしているでしょう?
if ( (err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0 )
|| (err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
|| (err = SSLHashSHA1.update(&hashCtx, &serverRandom) != 0)
|| (err = SSLHashSHA1.update(&hashCtx, &signedParams) != 0)
|| (err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)) {
goto fail;
}
例えば、failタグにマクロとして記述することで、goto文を削除することができます。
)gotoステートメントについて
gotoステートメントについては、1968年に専任のQAが必要 Communications of the ACMに投稿した論文の原題は "A Case Against the Goto Statement "でした。CACMの編集者であるNiklausWirthに触発され、タイトルをおなじみの "Go ToStatement Considered Harmful "に変更しました。ToStatement Considered Harmful" ダイクストラはいつもの鋭い口調で、「数年前、私はプログラマーの質はプログラム中のgoto文の密度に反比例することを観察した」と書き、こう付け加えました。「それ以来、私はなぜgoto文の使用がこのような深刻な結果をもたらすのかを発見し、すべての高級言語はgotoを廃止すべきだと考えています。
goto文はなぜ悪いのか?ダイクストラは、変数が表す意味はその文脈に依存すると言っています。あるプログラムが部屋にいる人の数をNで管理しているとします。しかし、別の人が部屋に入るのを見た後にNをインクリメントする命令の前にあるプログラムのブロックでは、Nの値は「現在部屋にいる人の数プラス1」を表します。このように、プログラムの状態を正しく解釈するためには、プログラムの実行履歴を知ること、言い換えれば「現在地」を知ることが必要です。
今どこにいるのか」をどうやって話すのでしょう?一直線に走るプログラムなら、指を突き出して「ここです」と言えばいいでしょう。ループがある場合は、「ループのこの場所で、ループがi回実行されました」と言う必要があるかもしれません。関数の中であれば、"関数pのこの場所で、pはqによって呼び出されたばかりで、呼び出されたポイントはi回実行されたループの中にある"と言わなければならないかもしれません。
もしgoto文があったら?それは問題です。というのも、コンピュータは特定の命令を実行する前に、プログラム内の多くのgotoのうちの1つからジャンプしている可能性があるからです。また、特定の変数の性質について話すこともほとんど不可能になります。だからgoto文は問題なのです。
ダイクストラの記事は、私を含め、その後の多くのプログラマーに非常に深い影響を与えました。私を含め、多くのプログラマーは、goto文は使えるときに使うべきだと感じていました。しかし、10年前のNiklaus WirthGo To 述べたように、私はgoto文が使えるケースは1つしかないと思っています。しかし、goto文は使えるのに使うべきではないというダイクストラの意見にも賛成です。RAIIテクニックを使ったより高度なC++では、goto文を使う意味はもうほとんどありません。
ダイクストラの論文は、構造化プログラミングの議論において最も有名な論文のひとつとなりました。19年後、フランク・ルービンは、「Statement Considered Harmful」と題する記事をCACMに寄稿しました。ルービンは、「ダイクストラの主張は、あまりにも学術的で説得力に欠けるが、すべてのプログラマの心に焼き付いているようだ。ルービン氏は、「ダイクストラの主張はあまりに学術的で説得力に欠けるものの、すべてのプログラマの心に焼き付いているようです。ですから、誰かが "この問題を解くにはgoto文を使ったほうがいいかもしれない "と言うと、ひどく軽蔑されるのです」。そこでルービンは、XがN * N個の整数の配列であるという問題を思いつきました。Xの行iがすべて0ならiを出力し、複数の行がある場合は最小のiを出力します。
ルービンは、gotoを習慣的に使うプログラマーとそうでないプログラマーを調べたところ、gotoを使うプログラムは速く明快であることを発見しました。そして、gotoを使わない人は、通常、複雑な解決策を書くのに多くの時間を費やしていました。どう思いますか? また、あなたならこの問題のプログラムをどう書きますか?
私にとっては、goto文の短所は長所をはるかに上回り、99%の場合、私はgoto文に反対する側です。
まとめ
この問題を完全になくすことはできませんが、次のような方法で減らすことはできます:
1)実行時ではなく、コンパイル時にエラーが出るようにしてください。
2)コードは人が読むものであり、機械が動かすものです。トラブルを恐れず、良いコードスタイル、読みやすいコードは多くの問題を減らします。
3)コードレビューは真剣なものですが、コードレビューの前提条件は、コードが非常に読みやすいことです。
4)テストは、特に開発者が真剣に取り組むべき重要かつ難しいものです。
5)その場しのぎはやめましょう。その場しのぎで問題を解決するのは恥ずかしいことです!gotoを使わなくても、コードをうまくまとめる方法はたくさんあります。
最後に、私はこの1年でタオバオでP1/P2の失敗をいくつか経験しましたが、特に昨年の8月から9月にかけては、P1/P2の失敗の70%がコードレビューの欠如、テストの不徹底、問題解決のための飛翔の多さによるもので、ビジネス上の結果ばかりを重視し、技術に対する然るべき厳格な態度や敬意の欠如に帰結するものでした。
アップルの失態が物語るように、テクノロジーに厳密さと敬虔さを適用しなければ、必ず--。
Go To Fail !
皆さんと分かち合いたいので、ここでたくさんわめき散らしてください!





