SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

C/C++セキュアコーディング入門

文字列はNULL終端させる
――C/C++セキュアコーディング入門(3)

「動けばいいってもんじゃない」 脆弱性を作り込まないコーディング 第3回

  • X ポスト
  • このエントリーをはてなブックマークに追加

 文字列操作に関するプログラミングエラーはいくつかありますが、今回は基本的であるが間違いを犯しやすい文字列のNULL終端エラーについて解説します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

攻撃対象として狙われやすい文字列処理

 文字列は、プログラムとユーザ、プログラムとプログラム間のインタフェースとして利用されるのをはじめ、XMLなどテキスト形式で表現された情報を処理する際にも利用されます。データのみならず、プログラムの挙動に直接影響する動作パラメタや設定情報など様々な情報がテキスト形式で表現されるにつれ、文字列を処理する機会が増加すると共に、文字列を処理すること自体の重要性が高まっています。

 攻撃者にしてみれば、プログラムの挙動を操作しうる文字列処理の不備を突く機会も多く存在することになるため、文字列操作エラーを作り込まないことが求められます。文字列操作に関するプログラミングエラーはいくつかありますが、今回は基本的であるが間違いを犯しやすい文字列のNULL終端エラーについて解説します。

NULL終端エラー

 文字列型を持たないC言語において、文字列は文字型の配列で表現されます。この配列には、文字列の最後を示すNULL文字が含まれている必要があります。文字列用に確保された文字型配列内にNULL文字が含まれていなければ、文字列として不完全であり、文字列を取り扱う関数や処理に引き渡した場合、脆弱性の要因となります。以下にNULL終端エラーに関連したいくつかの脆弱性の事例を示します。なお、カッコ内の番号(CVE-2006-1516 など)は、米国政府の支援を受けた非営利団体MITRE社が採番している識別子です。

  • MySQLのコネクション確立処理において、NULL終端されていないユーザ識別子を渡されることによって、意図しないメモリ領域の内容が読みだされてしまう脆弱性(CVE-2006-1516
  • Qualcomm POP3サーバ qpopperにおいて、snprintfに類似したユーザ関数呼び出しの結果、生成される文字列がNULL終端されないことにより、後続処理で発生しうるバッファーオーバーフローを利用した任意のコード実行に繋がる脆弱性(CVE-2003-0143
  • OpenBSD 2.5のcronにおいて、NULL終端されていないargv[]を渡されることによって、root権限が取得可能となる脆弱性(CVE-2000-0312

NULL 終端文字に依存する処理

 C標準の文字列処理関数を用いて文字列操作を安全に行うためには、引数として渡す文字列が確実にNULL終端されている必要があります。これは、strcpy()やstrlen()などをはじめとするC標準の文字列処理関数の動作が、NULL終端文字に依存しているためです。例えば、strlen()は、引数として渡された文字列にNULL終端文字が出現するまでの文字数をカウントしますし、strcpy()は、コピー先バッファにNULL終端文字が見つかるまで、コピー元の文字列の内容を書き込みます。

 また、C標準の文字列処理関数を利用していない場合でも、以下に示すコード例のように、NULL終端文字の存在に依存したコードを書くこともあるでしょう。

size_t i;
char ntbs[16];
/* 文字型配列 ntbs に値を設定(NULL終端されているかは不明) */
for (i = 0; ntbs[i] != '\0' ; i++) {
  /* 文字型配列 ntbs に含まれた英小文字を大文字に変換して配列に書き戻す */
}

 このコード例においても、ループの終了条件を満たすためには、処理対象となる文字列がNULL終端されている必要があることが分かります。

 もし、strcpy()やstrlen()あるいは上記コード例に対して、NULL終端されていない文字データが渡されたらどうなるでしょうか? こういったケースは、バッファーオーバーフローの発生や、その他の未定義動作につながる恐れがあります。上記のコード例であれば、配列の境界を越えて書き込みが行われ、メモリ内の他のデータを破壊してしまいます。strlen()であれば、想定される文字列の長さよりも、より大きな値が返却され、後続の処理でバッファーオーバーフローが引き起こされることも考えられます。

NULL終端を確実に行う

 NULL終端エラーを作りこまないためには、プログラマが責任を持ち、文字列がNULL終端されていることを確認するか、または自らNULL終端した上で、NULL終端文字の存在に依存する処理へ制御を移さなければなりません。前述のコード例であれば、ループに入る前に配列の最後をNULL終端することで、元の配列にNULLが含まれていない場合でも、ループの終了条件を確実に満たすことができます。

size_t i;
char ntbs[16];
/* 文字型配列 ntbs に値を設定(NULL終端されているかは不明) */
ntbs[sizeof(ntbs) - 1] = '\0'; /* NULL 終端する */
for (i = 0; ntbs[i] != '\0' ; i++) {
  /* 文字型配列 ntbs に含まれた英小文字を大文字に変換して配列に書き戻す */
}

 あるいは、文字列を作成する際、適切なコピー先バッファサイズを指定することで、結果として得られる文字列がNULL終端されることを保証する関数(ISO/IEC TR24731で提案されWindowsで利用可能なstrcpy_s()や、OpenBSD、FreeBSD、Solarisなどで利用可能なstrlcpy()など)を利用することもできます。

size_t i;
char ntbs[16];
/* strcpy_s() や strlcpy() を利用し、文字型配列 ntbs がNULL終端されることを保証する */
for (i = 0; ntbs[i] != '\0' ; i++) {
  /* 文字型配列 ntbs に含まれた英小文字を大文字に変換して配列に書き戻す */
}

 こういった対策は、NULL終端文字に依存するC標準の文字列処理関数へ文字列を引数として渡す場合においても同様です。NULL終端文字を直接設定するか、あるいは、NULL終端された文字列を作成する関数を利用することになります。

size_t len;
char ntbs[16];
/* 文字型配列 ntbs に値を設定(NULL終端されているかは不明) */
ntbs[sizeof(ntbs) - 1] = '\0'; /* あるいは、strcpy_s() や strlcpy() を利用する */
len = strlen(ntbs);

返り値の文字列がNULL終端されているとは限らない文字列処理関数の利用に注意

 また、返り値の文字列が必ずしもNULL終端されているとは限らない、C標準の文字列処理関数strncpy()の利用において、プログラマ自身が間違いを犯さないようにしなければなりません。以下のコード例は、NULL終端されていない文字列を strlen()に渡す危険があります。

size_t len;
char ntbs[16];
/* コピー元 source に値が設定される(サイズ、NULL終端の有無は不明) */
strncpy(ntbs, source, sizeof(ntbs));
/* 結果的に ntbs はNULL終端されていないかも知れない */
len = strlen(ntbs);

 strncpy()は、最大指定されたコピー文字数分を、コピー元配列からコピーしますが、コピーの過程でNULL文字が出現した場合は、その時点で、コピーを終了します。一方、NULL文字が現れなかった場合は、指定された文字数分のみコピーを行います。この場合、strncpy()によって返される文字列は、NULL終端されていません。よって、プログラマは、strncpy()が返す文字列が、NULL終端されないケースがあることを認識した上で、適切に対応する必要があります。

size_t len;
char ntbs[16];
/* コピー元 source に値が設定される(サイズ、NULL終端の有無は不明) */
strncpy(ntbs, source, sizeof(ntbs) - 1);
/* 結果的に ntbs はNULL終端されていないかも知れない */
ntbs[sizeof(ntbs) - 1] = '\0'; /* NULL 終端する */
len = strlen(ntbs);

 strncpy()のように誤用されやすい関数の利用以外にも、realloc()関数を利用して、バッファサイズの縮小を行った際にNULL終端を怠ってしまうケースや、memset()関数で事前にコピー先バッファにNULL文字をセットしたが、文字データをコピーする際に誤ってバッファの最終NULL文字を上書きしてしまうケースなどのうっかりミスにも注意が必要です。

 NULL終端文字列を作成するということは、単純なことではありますが、文字列がNULL終端されていることに依存する処理を安全かつ正確に実行させ、プログラムを意図した通りに動作させるためには必要不可欠です。

参考文献

  1. CERT C Secure Coding Standards 日本語版 Ver 1.0 『STR32-C. 文字列は NULL 終端させる
  2. Rationale for International Standard - Programming Languages - C Revision 5.10, April-2003(PDF)
  3. Rationale for TR 24731 Extensions to the C Library Part I: Bounds-checking interfaces Section 6.7.1.4, "The strncpy_s Function"(PDF)
  4. MSDN Visual C++ デベロッパーセンター 『CRT関数のセキュリティが強化されたバージョン
  5. CVE-2006-1516
  6. CWE-170: Improper Null Termination

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
C/C++セキュアコーディング入門連載記事一覧

もっと読む

この記事の著者

富樫 一哉(トガシ カズヤ)

システム開発マネージャJPCERTコーディネーションセンターカナダ British Columbia Institute of Technologyにて、Bachelor of Technology in Computer Systems修了。メーカー系、独立系システムインテグレーターにて、システム...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4644 2009/12/21 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング