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

POS38-C. fork およびファイル記述子を使用するときには競合状態に注意する

POS38-C. fork およびファイル記述子を使用するときには競合状態に注意する

子プロセスを生成すると、子プロセスに親プロセスのファイル記述子がコピーされ、ファイルに対する操作が同時に行われる可能性がある。ファイルに対する操作が同時に行われると、データの読み書きが予測不可能な順序で行われ、競合状態や予期せぬプログラムの動作が引き起こされる可能性がある。

違反コード

次のコード例は、あるファイルをオープンして1文字読み取り、fork を実行してから、親と子の両方のプロセスそれぞれが、ファイルの2文字目を読み取ることを意図している。しかし、2つのプロセスは同じファイル記述子を共有するため、一方のプロセスが2文字目を取得し、他方のプロセスが3文字目を取得するかもしれない。また、読み取りがアトミックな操作である保証はないため、プロセスは予期せぬ結果を引き起こす可能性がある。プログラマの意図した動作がどのようなものであるにせよ、競合状態が存在するこのコードは適切ではない。

char c;
pid_t pid;

int fd = open(filename, O_RDWR);
if (fd == -1) {
  /* エラー処理 */
}
read(fd, &c, 1);
printf("root process:%c\n",c);

pid = fork();
if (pid == -1) {
  /* エラー処理 */
}

if (pid == 0) { /* 子 */
  read(fd, &c, 1);
  printf("child:%c\n",c);
}
else { /* 親 */
  read(fd, &c, 1);
  printf("parent:%c\n",c);
}

アクセスしたファイルの内容が "abc" だとすると、このプログラムの出力は次のどちらかになるだろう。

root process:a
parent: b
child: c

または

root process: a
child: b
parent: c

出力は一つに決まらないため、このようなコードは使用するべきではない。

適合コード

次の適合コードでは、fork を実行した後、子プロセスでファイル記述子をクローズしてから再度オープンしており、この間にファイルが変更されていないことを保証している。詳細は「POS01-C. ファイルを操作するときにはリンクかどうかを確認する」を参照すること。

char c;

pid_t pid;

/* ファイルをオープンしてファイルステータスを記憶する */
struct stat orig_st;
if (lstat( filename, &orig_st) != 0) {
  /* エラー処理 */
}
int fd = open(filename, O_RDWR);
if (fd == -1) {
  /* エラー処理 */
}
struct stat new_st;
if (fstat(fd, &new_st) != 0) {
  /* エラー処理 */
}
if (orig_st.st_dev != new_st.st_dev ||
    orig_st.st_ino != new_st.st_ino) {
  /* ファイルのオープン中に不正操作がなされた */
}

/* ファイルは正常。fd を操作 */

read(fd,&c,1);
printf("root process:%c\n",c);

pid = fork();
if (pid == -1) {
  /* エラー処理 */
}

if (pid == 0){ /* 子 */
  close(fd);

  /* ファイルを再オープンし、新しいファイル記述子を作成 */
  fd = open(filename, O_RDONLY);
  if (fd == -1) {
    /* エラー処理 */
  }
  if (fstat(fd, &new_st) != 0) {
    /* エラー処理 */
  }
  if (orig_st.st_dev != new_st.st_dev ||
      orig_st.st_ino != new_st.st_ino) {
    /* 2 回目のファイルのオープンまでの間に不正な操作がなされた */
  }

  read(fd, &c, 1);
  read(fd, &c, 1);
  printf("child:%c\n", c);
  close(fd);
}

else { /* 親 */
  read(fd, &c, 1);
  printf("parent:%c\n", c);
  close(fd);
}

このコードの出力は次のようになる。

root process:a
child:b
parent:b
リスク評価

コード中の競合状態を検出するのは非常に難しいため、開発工程の一般的なデバッグ作業ではこの問題を発見できないかもしれない。読み取るファイルの種類やその読取り順序の重要性によっては、深刻な影響を及ぼす可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

POS38-C

P4

L3

翻訳元

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

POS38-C. Beware of race conditions when using fork and file descriptors (revision 25)

Top へ

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