MSC22-C. setjmp()、longjmp() の機能を安全に使用する
setjmp() マクロは、[ISO/IEC 9899:1999] のセクション7.13.1.1 に列挙されている文脈からのみ呼び出すこと。それ以外の文脈から setjmp() を呼び出すと未定義の動作となる(未定義の動作 119 を参照)。
volatile 修飾型でないローカルオブジェクトの場合、値が setjmp() の呼び出し以降変更されていた場合、longjmp() の呼び出し後にそのオブジェクトにアクセスしてはならない。この場合、オブジェクトの値は不定と見なされ、アクセスすると未定義の動作が引き起こされる(未定義の動作 121、10 を参照)。 longjmp() 関数を使用して、実行がすでに終了している関数に制御を戻してはならない(未定義の動作 120 を参照)。
シグナルマスク、浮動小数点ステータスフラグ、オープンファイルの状態は、setjmp() 関数によって保存されない。シグナルマスクを保存する必要がある場合は、 sigsetjmp() 関数を使用する。
このレコメンデーションは、ルール「SIG32-C. シグナルハンドラ内から longjmp() を呼び出さない」と「ENV32-C. atexit で登録したハンドラ関数は必ず return する」に関連する。
違反コード
次のコード例では、代入文で setjmp() を呼び出しており、結果として未定義の動作が引き起こされる。
jmp_buf buf; void f(void) { int i = setjmp(buf); if (i == 0) { g(); } else { /* longjmp が呼び出された */ } } void g(void) { /* ... */ longjmp(buf, 1); }
適合コード
次の解決法に示すように、setjmp() の呼び出しを if 文の中に入れ、それを整数定数と比較することで未定義の動作が排除される。
jmp_buf buf; void f(void) { if (setjmp(buf) == 0) { g(); } else { /* longjmp が呼び出された */ } } void g(void) { /* ... */ longjmp(buf, 1); }
違反コード
longjmp() 関数を呼び出して、すでに実行が完了している関数に制御を渡そうとすると、未定義の動作が引き起こされる。
jmp_buf buf; unsigned char b[] = {0xe5, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}; int main(void) { setup(); do_stuff(); return 0; } void setup(void) { f(); } void f(void) { g(); } void g(void) { if (setjmp(buf) == 0) { printf("setjmp() invoked\n"); } else { printf("longjmp() invoked\n"); } } void do_stuff(void) { char a[8]; memcpy(a, b, 8); /* ... 埋め込み … */ longjmp(buf, 1); } void bad(void) { printf("Should not be called!\n"); exit(1); }
処理系固有の詳細
Linux 上で GCC v4.1.2 を使用して x86-64 向けにコンパイルした場合、上記のコード例の出力は次のようになる。
setjmp() invoked longjmp() invoked Should not be called!
longjmp() が呼び出された時点で g() は実行を終了しているため、g() はすでにスタック上にはない。do_stuff() が呼び出されると、そのスタックフレームは g() の古いスタックフレームと同じメモリ領域を占有する。この場合、a は関数 g() の戻り先アドレスと同じ場所に置かれる。memcpy() の呼び出しが戻り先アドレスを上書きするため、longjmp() が関数 g() に制御を戻すときに、関数 g() は間違ったアドレス(この場合は関数 bad())に戻ることになる。
配列 b が第三者によって指定されたものだった場合、関数 g() の戻り先アドレスが任意の場所に設定されてしまう可能性がある。
適合コード
対応する setjmp() を囲んでいる関数の実行が完了していないことが保証される場合にのみ、longjmp() 関数を使用するべきである。
jmp_buf buf; unsigned char b[] = {0xe5, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}; int main(void) { if (setjmp(buf) == 0) { printf("setjmp() invoked\n"); } else { printf("longjmp() invoked\n"); } do_stuff(); return 0; } void do_stuff(void) { char a[8]; memcpy(a, b, 8); /* ... 埋め込み … */ longjmp(buf, 1); } void bad(void) { printf("Should not be called!\n"); exit(1); }
main()(setjmp() を呼び出した関数)のスタックフレームがスタック上に残っているため、戻り先アドレスを上書きする恐れはない。do_stuff() が呼び出される時点で 2 つのスタックフレームが重なり合うことはない。
違反コード
次のコード例では、対応する setjmp() を呼び出した関数内にのみ存在する volatile 修飾型でないオブジェクトの値は、setjmp() の呼び出し以降に変更されていると、longjmp() の実行後は不定となる。
jmp_buf buf; void f(void) { int i = 0; if (setjmp(buf) != 0) { printf("%i\n", i); /* ... */ } i = 2; g(); } void g(void) { /* ... */ longjmp(buf, 1); }
適合コード
setjmp() を呼び出した関数内にのみ存在するオブジェクトに対して、longjmp() がその関数に制御を戻した後でアクセスする必要がある場合、オブジェクトは volatile 修飾型でなければならない。
jmp_buf buf; void f(void) { volatile int i = 0; if (setjmp(buf) != 0) { printf("%i\n", i); /* ... */ } i = 2; g(); } void g(void) { /* ... */ longjmp(buf, 1); }
リスク評価
レコメンデーション | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
MSC22-C | 低 | 中 | 中 | P4 | L3 |
関連ガイドライン
- ISO/IEC 9899:1999 Section 7.13, "Nonlocal jumps
", Section J.2, "Portability issues"
翻訳元
これは以下のページを翻訳したものです。
MSC22-C. Use the setjmp(), longjmp() facility securely (revision 20)