FIO03-C. fopen() やファイル作成時の動作について勝手な想定をしない
C の fopen() 関数を使用して、既存のファイルをオープンしたり新規ファイルを作成することができる。C11 バージョンの fopen() と fopen_s() では、モードフラグ x を提供している。このフラグは、オープンしようとしているファイルが存在するかどうかの判断に必要なメカニズムを提供する。このモードフラグを使用しないと、意図しないファイルへの上書きやアクセスを行う可能性がある。
違反コード (fopen())
以下のコード例では、file_name が参照するファイルを書き込み用にオープンしている。プログラマの意図がファイルの新規作成であり、参照先のファイルがすでに存在している場合には、このコード例は違反コードである。
char *file_name;
FILE *fp;
/* file_name を初期化 */
fp = fopen(file_name, "w");
if (!fp) {
/* エラー処理 */
}
違反コード (fopen_s()、X11 Annex K)
C11 Annex K の fopen_s() 関数は、fopen() 関数のセキュリティ向上を意図して設計された。fopen() と同様に fopen_s() にも、操作対象のファイルがすでに存在するかどうかを判別する仕組み(モードフラグ)が提供されている。このモードフラグの使い方については、この後の説明を参照せよ。
char *file_name;
FILE *fp;
/* file_name を初期化 */
errno_t res = fopen_s(&fp, file_name, "w");
if (res != 0) {
/* エラー処理 */
}
適合コード (fopen_s(), C11 Annex K)
この問題に対処するため、C 言語規格では新たなフラグが導入された。セクション 7.21.5.3 パラグラフ 5 には次のように記載されている [ISO/IEC 9899:2011]。
すでに存在するファイルまたは生成が許されていないファイルに対する排他モードでのオープン(モード実引数の最後の文字が 'x')は失敗する。そうでない場合、ファイルは、システムが提供する範囲での排他的アクセス(非共有アクセス、とも呼ぶ)で生成される。
このオプションは GNU C ライブラリでも提供されている [Loosemore 2007]。
以下の適合コードは fopen_s() の引数に x を指定し、ファイルがすでに存在する場合には失敗するように指示している。
char *file_name;
/* file_name を初期化 */
FILE *fp;
errno_t res = fopen_s(&fp, file_name, "wx");
if (res != 0) {
/* エラー処理 */
}
このオプションを使うことによって、レガシーコードにおける脅威を簡単に緩和できる。ただし、Microsoft Visual Studio 2012 およびそれ以前において x モード文字はサポートされていないことに注意 [MSDN]。
適合コード (open()、POSIX)
Standard for Information Technology—Portable Operating System Interface (POSIX®), Base Specifications, Issue 7 [IEEE Std 1003.1:2013] で定義されている open() 関数は、多くのプラットフォームで利用可能であり、fopen() よりも詳細な制御が可能である。特に、O_CREAT および O_EXCL フラグを使うことができる。このフラグを両方指定すると、file_name で指定したファイルがすでに存在する場合に open() 関数の実行は失敗する。
char *file_name;
int new_file_mode;
/* file_name と new_file_mode を初期化 */
int fd = open(file_name, O_CREAT | O_EXCL | O_WRONLY, new_file_mode);
if (fd == -1) {
/* エラー処理 */
}
リモートファイルシステム上で O_EXCL フラグを使用する場合は注意が必要である。NFS バージョン 2 では open() 関数の O_EXCL フラグは動作しない。NFS バージョン 3 で対応が追加された。IETF RFC 1813 では、CREATE の mode 引数に使える値として EXCLUSIVE を定義している [Callaghan 1995]。
EXCLUSIVEは、サーバに対して、ターゲットファイルを排他的に作成するよう指定する。この場合属性を指定することはできない。サーバはターゲットファイルのメタデータとしてcreateverf3ベリファイアを格納することがあるからである。
ファイルをオープンせずに、そのファイルの有無を検査する例については、「FIO10-C. rename() 関数の使用に注意する」を参照すること。
適合コード (fdopen()、POSIX)
ファイル記述子ではなく FILE ポインタを扱うコードの場合、以下の適合コードに示すように、POSIX の fdopen() 関数を使用して、open() から返されたファイル記述子とファイルストリームを関連付けることができる[IEEE Std 1003.1:2013]。
char *file_name;
int new_file_mode;
FILE *fp;
int fd;
/* file_name と new_file_mode を初期化 */
fd = open(file_name, O_CREAT | O_EXCL | O_WRONLY, new_file_mode);
if (fd == -1) {
/* エラー処理 */
}
fp = fdopen(fd, "w");
if (fp == NULL) {
/* エラー処理 */
}
適合コード (Windows)
Win32 API では CreateFile() に適切なフラグを渡すことにより、ファイルを新たに生成したり既存のファイルをオープンしたりできる。CREATE_NEW フラグを指定すると、ファイルがすでに存在している場合に実行は失敗する。次の適合コードでは、非共有アクセスで新たなファイルをオープンして読み書きする。また、ファイルがすでに存在する場合にはエラー終了する。
TCHAR *file_name;
HANDLE hFile = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
if (INVALID_HANDLE_VALUE == hFile) {
DWORD err = GetLastError();
if (ERROR_FILE_EXISTS == err) {
/* ファイルがすでに存在している場合のエラー処理 */
} else {
/* その他のエラー処理 */
}
}
リスク評価
既存のファイルを開いたのか新規ファイルを作成したのかを判別できれば、意図したファイル以外のファイルを操作対象にしていないことをより確実にすることができる。
|
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
|---|---|---|---|---|---|
|
FIO03-C |
中 |
中 |
高 |
P4 |
L3 |
自動検出(最新の情報はこちら)
|
Tool |
Version |
Checker |
Description |
|---|---|---|---|
| Coverity | 6.5 | OPEN_ARGS | Fully implemented |
| PRQA QA-C | v8.2 | warncall for fopen and fopen_s | Partially implemented |
関連するガイドライン
| CERT C++ Secure Coding Standard | FIO03-CPP. Do not make assumptions about fopen() and file creation |
| ISO/IEC TR 24731-1:2007 | Section 6.5.2.1, "The fopen_s Function" |
参考情報
| [Callaghan 1995] | IETF RFC 1813 NFS Version 3 Protocol Specification |
| [IEEE Std 1003.1:2013] | System Interfaces: open |
| [ISO/IEC 9899:2011] | Subclause 7.21.5.3, "The fopen Function"Subclause K.3.5.2.1, "The fopen_s Function" |
| [Loosemore 2007] | Section 12.3, "Opening Streams" |
| [Seacord 2013] | Chapter 8, "File I/O" |
翻訳元
これは以下のページを翻訳したものです。
FIO03-C. Do not make assumptions about fopen() and file creation (revision 156)
