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

FIO19-C. ファイルサイズの計算に fseek() および ftell() を使用しない

ファイルストリームを操作する関数を使用するときには、テキストモードとバイナリモードの違いを正しく理解することが重要である(レコメンデーション「FIO14-C. ファイルストリームにおけるテキストモードとバイナリモードの違いを理解する」も参照)。

C99 [ISO/IEC 9899:1999] のセクション 7.19.9.2 は、バイナリモードでバイナリファイルを開いた場合の fseek() の特定の動作について次のように述べている。

バイナリストリームでは、whence が SEEK_END の値をもつ fseek の呼出しを意味あるものとしてサポートする必要はない。

さらに、セクション 7.19.3 の脚注 234 には次のように記載されている。

バイナリストリーム(ナル文字の詰め物が可能なため)又は、初期シフト状態で終わっていることを保証できない、シフト状態に依存した表現形式をもつストリームに対して、fseek(file, 0, SEEK_END) などでファイルの終わりに位置付けることは、未定義の動作とする。

fseek() を用いてバイナリモードでバイナリストリームの終わり位置を検出することは、意味あるものとしてサポートされないため、ファイルサイズの計算方法としては推奨されない。

C99 [ISO/IEC 9899:1999] のセクション 7.19.9.4 は、テキストモードでテキストファイルを開いた場合の ftell() の特定の動作について次のように記載している。

テキストストリームの場合、そのファイル位置表示子は、ftell 関数の呼出し時の位置にそのストリームのファイル位置表示子を戻すために、fseek 関数で使用できる情報を含む。

ゆえに、テキストモードで開いているストリームに対する ftell() の返り値を、fseek() の呼び出し以外の場所でオフセット計算のために使用してはならない。

違反コード

次のコード例では、バイナリモードでバイナリファイルを開き、fseek()ftell() を使用してファイルサイズを取得しようとしている。

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.bin", "rb");
if (fp == NULL) {
  /* エラー処理 */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* エラー処理 */
}

file_size = ftell(fp);
if (file_size == -1) {
  /* エラー処理 */
}

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* エラー処理 */
}

/* ... */ 

しかし、fseek を使ってファイル位置表示子をファイルの終わりに設定すると、バイナリストリームでは未定義の動作が生じるため、適切な量のメモリが割り当てられず、潜在的な脆弱性につながる可能性がある。

適合コード (UNIX および Windows)

次の解決法では、fstat() を使用してバイナリファイルのサイズを取得している。

FILE *fp;
long file_size;
char *buffer;
struct stat stbuf;
int fd;

fd = open("foo.bin", O_RDONLY);
if (fd == -1) {
  /* エラー処理 */
}

fp = fdopen(fd, "rb");
if (fp == NULL) {
  /* エラー処理 */
}

if (fstat(fd, &stbuf) == -1) {
  /* エラー処理 */
}

file_size = stbuf.st_size;

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* エラー処理 */
}

/* ... */ 
違反コード

次のコード例では、テキストモードでテキストファイルを開き、fseek()ftell() を使用してファイルサイズを取得しようとしている。

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.txt", "r");
if (fp == NULL) {
  /* エラー処理 */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* エラー処理 */
}

file_size = ftell(fp);
if (file_size == -1) {
  /* エラー処理 */
}

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* エラー処理 */
}

/* ... */ 

しかし、テキストモードで開いているファイルに関して ftell() が返すファイル位置表示子は、fseek() の呼び出しの中でのみ有効である。そのため、file_size の値は必ずしもファイル内の文字数を取得する意味のある手段にはならない場合がある。ゆえに、適切な量のメモリが割り当てられず、潜在的な脆弱性につながる可能性がある。

適合コード (UNIX および Windows)

次の解決法では、代わりに fstat() を使用してテキストファイルのサイズを取得している。

FILE *fp;
long file_size;
char *buffer;
struct stat stbuf;
int fd;

fd = open("foo.txt", O_RDONLY);
if (fd == -1) {
  /* エラー処理 */
}

fp = fdopen(fd, "r");
if (fp == NULL) {
  /* エラー処理 */
}

if (fstat(fd, &stbuf) == -1) {
  /* エラー処理 */
}
file_size = stbuf.st_size;

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* エラー処理 */
}

/* ... */ 
処理系固有の詳細

[MSDN] には、ftell() について次のように記述されている。

テキストモードで開いたストリームでは、キャリッジリターンとラインフィードの変換が行われるため、ftell の戻り値は物理的なバイトオフセットを表さない場合があります。正しいファイルの位置を返すには、ftell と共に fseek を使用してください。

ここでも、テキストモードで開いたストリームに対する ftell() の返り値は、fseek() の呼び出しの中でのみ有効であり、この値を他の目的に使用してはならない。

以下のコード例では、テキストモードで開いたテキストファイルから 10 バイトの読み取りを行おうとしている。ftell() を使用してファイル位置表示子の現在の値を取得している。

コードは、Windows XP Professional SP3 上で Microsoft Visual Studio 2008 SP1 を用いてコンパイルされた。

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

int main() {

  FILE *fp;
  char a[11];
  long offset;

  /* foo.txt の内容:改行(\n)で区切られた 10 行の 0123456789 */
  fp = fopen( "foo.txt", "r" );
  if (fp == NULL) {
    perror("fopen() error");
    exit(EXIT_FAILURE);
  }

  /* 10 (n-1) バイトの読み取り */
  if (fgets(a, 11, fp) == NULL) {
    perror("fgets() error");
    fclose(fp);
    exit(EXIT_FAILURE);
  }

  offset = ftell(fp);
  if (offset == -1) {
    perror("ftell() error");
    fclose(fp);
    exit(EXIT_FAILURE);
  }

  printf("offset = %ld\n", offset);   /* 0 を出力 */ 

  return 0;
}

このコードを前述の設定で実行すると、次のように出力される。

offset = 0

しかし、0 は正しくない。ファイルをテキストモードではなくバイナリモードで開いている場合は、正しい値は明らかに 10 でなければならない。

リスク評価

ファイルストリームにおけるテキストモードとバイナリモードの違いを理解することは、ファイルストリームを操作する関数を使用するうえで非常に重要である。fseek() を使ってファイル位置表示子をファイルの終わり位置に設定すると、バイナリストリームでは未定義の動作が生じる。さらに、テキストモードで開いたストリームに対する ftell() の返り値は fseek() の呼び出しの中でのみ有効であり、ファイルサイズの計算など、他の目的にこの値を使用してはならない。ファイルのサイズを計算する場合は、fstat()、またはプラットフォームにおける同等の関数を使用すべきである。

レコメンデーション 深刻度 可能性 修正コスト 優先度 レベル
FIO19-C P2 L3
関連ガイドライン
参考資料
翻訳元

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

FIO19-C. Do not use fseek() and ftell() to compute the size of a file (revision 16)

Top へ

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