はじめに
2009年末から6回にわたりセキュアコーディングに関する連載をさせていただきましたが、今回からは少々構成を変え、実際に公開されているライブラリやアプリケーションなど、脆弱性が発見されたコードを題材にして新しい連載を進めていきたいと思います。
今回の連載では、最初に問題のあるコードを示します。まずはこのコードだけを見て、どこに問題があるのか、考えてみてください。
コードの後には、コードに含まれる脆弱性を見つけるためのヒントや、コードが行おうとしていることを理解するために役立つ背景知識などを説明します。コードを見ただけではどこに問題があるのか分からない、といった場合は、これらの説明を手がかりに考えてみてください。
どこに問題があるのか分かったら、次にどのように修正すべきかを考えましょう。修正方法は一通りとは限りません。むしろ、複数の修正方法が考えられることが多いと思います。
最後に、実際にどのような修正が行われたか説明します。自分が考えた修正案と比較してみてください。
サンプルコード
今回取り上げるコードを以下に示します。ここで取り上げているのはGUIツールキット「gtk+」で使われているライブラリglibの中で定義されている関数g_base64_encode()です。この短いコードのなかに脆弱性があります。まずは、このコードだけを見て、どんな問題があるのか考えてみてください。
/** * g_base64_encode: * @data: the binary data to encode * @len: the length of @data * * Encode a sequence of binary data into its Base-64 stringified * representation. * * Return value: a newly allocated, zero-terminated Base-64 encoded * string representing @data. The returned string must * be freed with g_free(). * * Since: 2.12 */ gchar *g_base64_encode (const guchar *data, gsize len) { gchar *out; gint state = 0, outlen; gint save = 0; g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (len > 0, NULL); /* We can use a smaller limit here, since we know the saved state is 0 */ out = g_malloc (len * 4 / 3 + 4); outlen = g_base64_encode_step (data, len, FALSE, out, &state, &save); outlen += g_base64_encode_close (FALSE, out + outlen, &state, &save); out[outlen] = '\0'; return (gchar *) out; }
この関数は、引数として受け取ったデータをbase64エンコーディングした結果を返します。base64エンコーディングは、SJISやUTF-8文字列、あるいはバイナリデータなどのように最上位ビットが1になっている文字(オクテット)を含むデータを、最上位ビットが必ず0となる文字だけで表現するものです。
昔のメールサーバの実装には、メールはUS-ASCII文字(各文字の最上位ビットは0)のみで構成されているものと想定し、メールデータ中のオクテットの最上位ビットを強制的に0にしてしまうものがありました。そのような状況でも正しくメールを送受信できるように考案された技術の1つがbase64エンコーディングです。詳細はRFC 4648を参照ください。
base64エンコーディングでは、8ビット3文字(合計24ビット)を6ビット単位に分割し、US-ASCII4文字に変換します。つまりbase64エンコードした結果は、単純計算で元データの4/3倍の長さになるわけです。
受け取ったデータを実際にbase64エンコーディングに変換する作業は、g_base64_encode()から呼び出しているg_base64_encode_step()およびg_base64_encode_close()で行われます。これはメモリ上に一度に置けないような大きなデータの変換を考慮しているためで、元データを分割し、g_base64_encode_step()で順番に変換していくことを想定した設計になっているのです。
例えば、元データを10個に分割して扱う場合は、g_base64_encode_step()を呼び出してはその出力を処理する、という手順を10回繰り返します。元データをすべて入力し終ったら、最後にg_base64_encode_close()を呼び出し、まだ出力せずに残っているデータがあればパディングビットを加えて最後の出力を得ます。
今回の題材としているg_base64_encode()では、変換結果は一度にすべてメモリ上に収まるという前提で、変換結果全体を書き出すメモリを先にg_malloc()で確保しています。そのため、g_base64_encode_step()の呼び出しは1回だけになっています。また、変換結果をC言語の文字列として扱うために、変換結果の末尾にnull終端文字である'\0'を追加しています。
さて、g_base64_encode()の問題がどこにあるか、あなたは見つけられましたか? また、修正コードはできましたか?
では、解説編にいきましょう。