JPCERT コーディネーションセンター

ENV32-C. atexit で登録したハンドラ関数は必ず return する

ENV32-C. atexit で登録したハンドラ関数は必ず return する

いかなる atexit() ハンドラも return 以外の方法で終了してはならない。すべての atexit() ハンドラがクリーンアップ動作を実行することは重要であり、また、安全確保上欠かせない場合もある。ライブラリ関数がハンドラを登録していることを、アプリケーションのプログラマが知っているとは限らないからである。特に問題となるのは、exit() の中から exit() を呼び出した場合や、longjmp() を呼び出すことで atexit() に登録されたハンドラを終了する場合などである。

プログラムの通常終了には、C の exit() 関数が使用される(「ERR04-C. プログラムの適切な終了方法を選択する」を参照)。exit() の中から exit() を呼び出すと未定義の動作が発生する(附属書 J 「未定義の動作」の 182 も参照すること)。これが発生するのは、atexit() を使用して登録した関数から exit() が呼び出された場合か、またはシグナルハンドラ内から exit() が呼び出された場合のみである(「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」を参照)。

longjmp() の呼び出しによって atexit() に登録された関数の呼び出しが終了した場合、その動作は未定義である。

違反コード

以下のコード例では、プログラム終了時に必要なクリーンアップを実行するために exit1()exit2() 関数が atexit() により登録されている。しかし、exit2() の中の成立条件が真と評価される場合、exit()は2度呼び出され、未定義の動作となる。

#include <stdio.h>
#include <stdlib.h>

void exit1(void) {
  /* ...クリーンアップ... */
  return;
}

void exit2(void) {
  if (/* condition */) {
    /* ...さらにクリーンアップ... */
    exit(0);
  }
  return;
}

int main(void) {
  if (atexit(exit1) != 0) {
    /* エラー処理 */
  }
  if (atexit(exit2) != 0) {
    /* エラー処理 */
  }
  /* ...プログラムコード... */
  exit(0);
}

atexit() 関数により登録された関数は、すべて登録とは逆の順序で呼び出されるため、exit2()return 以外の方法で終了すると、exit1() は実行されない。これは、ライブラリ関数によって atexit() ハンドラが登録された場合についても当てはまる。

適合コード

atexit() によって終了ハンドラとして登録された関数は、必ず return で終了しなければならない。

#include <stdio.h>
#include <stdlib.h>

void exit1(void) {
  /* ...クリーンアップ... */
  return;
}

void exit2(void) {
  if (/* condition */) {
    /* ...さらにクリーンアップ... */
  }
  return;
}

int main(void) {
  if (atexit(exit1) != 0) {
    /* エラー処理 */
  }
  if (atexit(exit2) != 0) {
    /* エラー処理 */
  }
  /* ...プログラムコード... */
  exit(0);
}
違反コード

exit1() 関数は atexit() によって登録されており、プログラム終了時に呼び出される。exit1() における longjmp() により、実行は main() に戻り return する。これは未定義の動作となる。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf env;
int val;

void exit1(void) {
  /* ... */
  longjmp(env, 1);
}

int main(void) {
  if (atexit(exit1) != 0) {
    /* エラー処理 */
  }
  /* ... */
  if (setjmp(env) == 0) {
    exit(0);
  }
  else {
    return 0;
  }
}
適合コード

無効な longjmp() 呼び出しを防ぐ最良の回避策は、プログラムフローを注意深く検討することである。exit() 関数が呼び出されたあとで longjmp() を使って関数を終了させることは避けること。

#include <stdlib.h>

void exit1(void) {
  /* ... */
  return;
}

int main(void) {
  if (atexit(exit1) != 0) {
    /* エラー処理 */
  }
  /* ... */
  exit(0);
}
リスク評価

atexit() で登録した関数を return 以外の方法で終了させると未定義の動作となり、プログラムの異常終了やその他の予期せぬ動作につながるかもしれない。また、登録されている他のハンドラの呼び出しが妨げられるかもしれない。

ルール

深刻度

可能性

修正コスト

優先度

レベル

ENV32-C

P12

L1

自動検出(最新の情報はこちら

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

このルールの違反を検出できる。とくに、atexit() で登録された関数のすべてが exit() などを呼び出さないことを保証する。

関連するガイドライン
CERT C++ Secure Coding Standard ENV32-CPP. All atexit handlers must return normally
ISO/IEC TR 24772:2013 Structured Programming [EWD]
Termination Strategy [REU]
MITRE CWE CWE-705, Incorrect control flow scoping
翻訳元

これは以下のページを翻訳したものです。

ENV32-C. All atexit handlers must return normally (revision 62)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter