読者です 読者をやめる 読者になる 読者になる

てきとうなメモ

本の感想とか技術メモとか

OpenSSLの脆弱性(Heartbleed)

UbuntuのOpenSSLのpatchを見ての推測をてきとうに書いてみる。

TLS/DTLSプロトコルにHeartbeatという拡張プロトコルがあって、接続先が生きているかどうかをチェックするために使われたりする。

こいつは基本的にechoプロトコルみたいにリクエストのペイロードをそのままレスポンスとして返す。

リクエスト/レスポンスのメッセージは以下の様な構造体になっていて、

 struct {
      HeartbeatMessageType type;
      uint16 payload_length;
      opaque payload[HeartbeatMessage.payload_length];
      opaque padding[padding_length];
   } HeartbeatMessage;
  • type: タイプ(リクエストかレスポンスか)(1バイト)
  • payload_length: ペイロードの長さ(2バイト)
  • payload: ペイロード
  • padding: パディング(最小16バイト)

となっている。

OpenSSLの修正前のコードを見ると送られてきたペイロードの長さをそのまま信用してmemcpyして返答している。

int
tls1_process_heartbeat(SSL *s)
        {
        ...
        /* Read type and payload length first */
        // 送られてきたメッセージのtypeとpayload_lengthをそれぞれhbtypeとpayloadに入れる
        hbtype = *p++;
        n2s(p, payload);
        pl = p;
        ...
        if (hbtype == TLS1_HB_REQUEST)
                ...
                // payload部分を指すpから返信メッセージのバッファbpにpayloadのサイズだけコピー
                memcpy(bp, pl, payload);

そのため、宣言しているペイロードの長さが、実際のペイロードの長さ+パディングの長さを超えると、メモリ上の他の部分もmemcpyによって返信メッセージに書き込まれてしまう。それで、メモリ上にある情報が漏れてしまうのだろう。

修正後は以下のように宣言されたペイロードの長さと実際のペイロードの長さを比較している。

        hbtype = *p++;
        n2s(p, payload);
        if (1 + 2 + payload + 16 > s->s3->rrec.length)
                return 0; /* silently discard per RFC 6520 sec. 4 */

s->s3->rrec.lengthはHeartbeatMessageのサイズを表している。

また、ペイロードの長さを2バイトで表現しているので、1度に最大64KBデータを取得できるのだと思われる。