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

SIG32-C. シグナルハンドラ内からlongjmp() を呼び出さない

このガイドラインは廃止され、以下のガイドラインに統合されました。

シグナルハンドラ内から longjmp() 関数を呼び出した結果、非同期安全でない関数が呼び出されると、未定義の動作につながり、プログラムの完全性が損なわれる可能性がある。このため、longjmp() も POSIX の siglongjmp() も、シグナルハンドラ内から呼び出してはならない。

このルールは「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」に密接に関連している。

違反コード

以下は、古いバージョンの sendmail の脆弱性 [VU#834865] に類似のコード例である。コードの目的は main() のループでコードを実行し、データのログも記録することである。SIGINT を受信すると、プログラムはループから抜け、エラーを記録して終了する。

攻撃者は、log_message() 中の2番目の if 文が実行される直前に SIGINT を生成することで、このコードを悪用できる可能性がある。longjmp() によりプログラムの制御は main() に戻り、log_message() が再び呼び出される。しかし、今度は最初の if 文が実行されず(割り込みにより bufNULL に設定されていないため)、結果としてプログラムは buf0 が参照する無効なメモリに書き込みを行うことになる。

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

enum { MAXLINE = 1024 };
static jmp_buf env;

void handler(int signum) {
  longjmp(env, 1);
}

void log_message(char *info1, char *info2) {
  static char *buf = NULL;
  static size_t bufsize;
  char buf0[MAXLINE];

  if (buf == NULL) {
    buf = buf0;
    bufsize = sizeof(buf0);
  }

  /*
   *  メッセージを buf に入れる。buf の大きさが不足していたら、
   *  メッセージデータを格納するのに十分な領域を
   *  ヒープメモリから割り当て直して処理する。
   */

/*** VULNERABILITY IF SIGINT RAISED HERE ***/

  if (buf == buf0) {
    buf = NULL;
  }
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* エラー処理 */
  }
  char *info1;
  char *info2;

  /* info1 と info2 はユーザ入力からきている */

  if (setjmp(env) == 0) {
    while (1) {
      /* メインループ */
      log_message(info1, info2);
      /* さらにプログラムコードが続く */
    }
  }
  else {
    log_message(info1, info2);
  }

  return 0;
}
適合コード

以下の解決法では、longjmp() の呼び出しを削除している。代わりに、シグナルハンドラは volatile sig_atomic_t 型のエラーフラグを設定している。

#include <signal.h>
#include <stdlib.h>

enum { MAXLINE = 1024 };
volatile sig_atomic_t eflag = 0;

void handler(int signum) {
  eflag = 1;
}

void log_message(char *info1, char *info2) {
  static char *buf = NULL;
  static size_t bufsize;
  char buf0[MAXLINE];

  if (buf == NULL) {
    buf = buf0;
    bufsize = sizeof(buf0);
  }

  /*
   *  メッセージを buf に入れる。buf の大きさが不足していたら、
   *  メッセージデータを格納するのに十分な領域を
   *  ヒープメモリから割当て直して処理する。
   */
  if (buf == buf0) {
    buf = NULL;
  }
}

int main(void) {
  if (signal(SIGINT, handler) == SIG_ERR) {
    /* エラー処理 */
  }
  char *info1;
  char *info2;

  /* info1 と info2 はユーザ入力からきている */

  while (!eflag) {
    /* メインループ */
    log_message(info1, info2);
    /* さらにプログラムが続く */
  }

  log_message(info1, info2);

  return 0;
}
リスク評価

シグナルハンドラから longjmp() を呼び出すと、setjmp() 直後のコードがシグナルハンドラと同じ条件で実行される。このときグローバルデータは不整合な状態になっているかもしれず、(再度シグナルがあがることにより)自身に割り込みがかかるかもしれない。シグナルハンドラから longjmp() を呼び出すリスクは、非同期安全でない関数を呼び出すシグナルハンドラのリスクと同じである。

シグナルハンドラ内から非同期安全でない関数を呼び出すと、権限の昇格やその他の攻撃を引き起こす可能性がある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
SIG32-C P18 L1
自動検出

LDRA ツールスイート V 7.6.0 はこのルールの違反を検出できる。

Splint Version 3.1.1 はこのルールの違反を検出できる。

Compass/ROSE は、単一ファイルのプログラムの場合このルールへの違反を検出できる。

関連する脆弱性

シグナルハンドリング関連の脆弱性の概要、悪用、防止に関する Zalewski の論文 [Zalewski 01] を参照のこと。VU#834865 はこのルールの違反が原因で生じる脆弱性についてとり上げている[VU#834865]。

シグナルハンドラ内での longjmp() 関数の使用が重大な脆弱性を引き起こしたもう1つの注目すべき事例として、wu-ftpd 2.4 [Greenman 97] がある。wu-ftpd では、あるシグナルハンドラ内で EUID をゼロに設定している。2番目のシグナルが最初のシグナルに割り込むと、longjmp() の呼び出しが行われ、プログラムはメインスレッドに戻るがユーザの権限は降格されないままになる。昇格した権限は、さらなる攻撃に悪用される可能性がある。

参考情報
翻訳元

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

SIG32-C. Do not call longjmp() from inside a signal handler

Top へ

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