GHOST (CVE-2015-0235) について
1/27にglibcのgethostbynameでバッファオーバーフローのを脆弱性 (CVE-2015-0235) が発表されました。この脆弱性は、通称 GHOST と呼ばれています。
http://www.openwall.com/lists/oss-security/2015/01/27/9
CVE-2015-0235の原因
脆弱性が見つかったのは、nss/digits_dots.cの__nss_hostname_digits_dots()という内部関数。影響を受けるのは、内部で__nss_hostname_digits_dots()をコールしているgethostbyname()、gethostbyname_r()、gethostbyname2()、gethostbyname2_r()、gethostbyname3_r()の5つの関数です。
CVE-2015-0235は、内部で使用するバッファのサイズの見積ロジックに不備があり、その結果サイズが小さいバッファを割り当ててしまいバッファオーバーフローを引き起こしてしまう不具合によって引き起こされる脆弱性です。
それでは、問題箇所のソースコードを見てみましょう。下記は、nss/digits_dots.cにある__nss_hostname_digits_dots()関数の一部です。
typedef unsigned char host_addr_t[16]; host_addr_t *host_addr; typedef char *host_addr_list_t[2]; host_addr_list_t *h_addr_ptrs; char **h_alias_ptr; size_t size_needed; ........ size_needed = (sizeof (*host_addr) + sizeof (*h_addr_ptrs) + strlen (name) + 1);★1 ........ host_addr = (host_addr_t *) *buffer;★2 h_addr_ptrs = (host_addr_list_t *) ((char *) host_addr + sizeof (*host_addr)); h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs)); hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
★2の部分以下のコードより、bufferには、ホストアドレス、ホストアドレスのリスト、エイリアス (ホスト名に対する別名) のリスト、ホスト名 (入力文字列) を格納しようとしていることがわかります。従って、bufferに必要なサイズは、順に、16バイト (unsigned char x 16個)、ポインタのサイズ x 2バイト、ポインタのサイズ、ホスト名 (入力文字列) の長さです。
そして、その前の★1でバッファの必要サイズsize_neededを計算しています。ここには載せていませんが、もし不足している場合はrealloc()で再割当てするか、異常終了するようになっています。
しかし、★1を見ると、エイリアス (ホスト名に対する別名) のリストを乗せるサイズ分が漏れてしまっています。その結果、サイズが小さいバッファを割り当ててしまいます。
CVE-2015-0235の修正
size_needed = (sizeof (*host_addr) - + sizeof (*h_addr_ptrs) + strlen (name) + 1); + + sizeof (*h_addr_ptrs) + + sizeof (*h_alias_ptr) + strlen (name) + 1);
「CVE-2015-0235の内容」で述べたとおり、エイリアス (ホスト名に対する別名) のリストを乗せるサイズ分が漏れてしまっていることが原因ですので、これを入れてあげることで正しいコードになり脆弱性はなくなります。
CVE-2015-0235への該当有無の判定
続いて、上記のURLで公開されている脆弱性の有無を判定するプログラムのソースコードを見てみましょう。
#include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define CANARY "in_the_coal_mine" struct { char buffer[1024];★3 char canary[sizeof(CANARY)]; } temp = { "buffer", CANARY };★4 int main(void) { struct hostent resbuf; struct hostent *result; int herrno; int retval; /*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/ size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;★5 char name[sizeof(temp.buffer)]; memset(name, '0', len); name[len] = '\0'; retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno); if (strcmp(temp.canary, CANARY) != 0) { ★6 puts("vulnerable"); exit(EXIT_SUCCESS); } if (retval == ERANGE) { puts("not vulnerable"); exit(EXIT_SUCCESS); } puts("should not happen"); exit(EXIT_FAILURE); }
最初に着目すべき箇所は★5です。sizeof (temp.buffer) はバッファのサイズです。ここから逆算して、バッファのサイズが必要サイズぎりぎりになる (size_needed == suzeof (temp.buffer)) ようにホスト名 (入力文字列) のサイズを計算しています。もちろん、故意にバッファオーバーフローを発生させるためです。
★4では1024バイトのバッファの後ろにカナリア (バッファオーバーフローが発生するとこの部分が上書きされるため、バッファーオーバーフローを検知できる。"in_the_coal_mine"の内容に特別な意味はない) を置いています。gethostbyname_r ()を実行し、★6でカナリアの部分が書き換えられたかどうかチェックしています。書き換えられたのであれば、バッファオーバーフローが発生した、すなわち脆弱性ありということになります。