今回バグってたのはheartbeat extension という機能の実装。
RFCはこちら
https://tools.ietf.org/html/rfc6520
Heartbleedバグに対する修正コミット
http://git.openssl.org/gitweb/?p=openssl.git;a=commit;h=96db9023b881d7cd9f379b0c154650d6c108e9a3
バグ解説
4. Heartbeat Request and Response Messages
The Heartbeat protocol messages consist of their type and an
arbitrary payload and padding.
struct {
HeartbeatMessageType type;
uint16 payload_length;
opaque payload[HeartbeatMessage.payload_length];
opaque padding[padding_length];
} HeartbeatMessage;
Request,Response共にこのHeartbeatMessageを送る。
When a HeartbeatRequest message is received and sending a
HeartbeatResponse is not prohibited as described elsewhere in this
document, the receiver MUST send a corresponding HeartbeatResponse
message carrying an exact copy of the payload of the received
HeartbeatRequest.
とあるので、HeartbeatResponseを返す側は送られてきたHeartbeatRequestのペイロードをコピーして返す必要がある。
今回問題となったコードはこのペイロードコピー部分。
2591 /* Read type and payload length first */
2592 hbtype = *p++;
2593 n2s(p, payload);
2594 pl = p;
(中略)
2616 memcpy(bp, pl, payload);
HeartbeatRequestからペイロード長(コードではpayload)を取得して、HeartbeatResponseにペイロードをコピーするためにmemcpyしているが、ペイロード長について境界チェックを行っていないため、HeartbeatRequestに記載されたペイロード長が実際のペイロード長よりも長い場合 隣接するメモリの内容までmemcpy してHeartbeatResponseとして送り返してしまう。
そのため攻撃者は意図的に長いペイロード長を指定することで、1回のHeartbeatにつきメモリの内容を最大約64KB覗き見ることが可能。
修正
If the payload_length of a received HeartbeatMessage is too large,
the received HeartbeatMessage MUST be discarded silently.
ペイロード長が長すぎる場合は破棄とあり、そのように修正されている。
2596 /* Read type and payload length first */
2597 if (1 + 2 + 16 > s->s3->rrec.length)
2598 return 0; /* silently discard */
2599 hbtype = *p++;
2600 n2s(p, payload);
2601 if (1 + 2 + payload + 16 > s->s3->rrec.length)
2602 return 0; /* silently discard per RFC 6520 sec. 4 */
2603 pl = p;