fopen_s/fprintf_sなどのセキュアCライブラリ

家庭教師としてオンラインでプログラミングを教えているのだが、その際fopen_sfprintf_sという関数が出てきて、最初はfopenfprintfの新しいバージョン、もしくは古いバージョンの関数かなくらいに思っていたのだが、どうもMacgccでもclangでも「定義されていない」とエラーを吐き、コンパイラのバージョンは最新でかつ普通後方互換性も担保されているはずなのでおかしいと思って色々調べてみた。するとどうもWindowsでのみ動く関数であることがわかった。意外とそれに関する情報があまりまとめられていなさそうだったので、今回まとめて見ることにした。

基本的にfopen_sやfprintf_sの方がセキュリティに優れているらしい。具体的には、このサイトに非推奨や時代遅れの関数がまとまっている。私たちが普段使っている多くの標準ライブラリ関数のセキュリティレベルが低いとされている。

具体的に説明する。_sがついたCRT(Cランタイム)関数はセキュリティがより強化されたバージョンである。このような関数はセキュリティエラーが防止されたり修正されたりするわけではなく、発生したエラーのキャッチ、それによるエラー状態の追加チェックが行われて、エラーハンドラを呼び出す。

以下のようなセキュリティ機能が付与されている。参照

  • パラメータの検証
  • バッファーの範囲を超えた書き込みが行われないようにバッファーサイズを指定して関数に渡す
  • 文字列を確実にNULLで終了させる
  • より詳細なエラー情報
  • ファイルシステムのセキュリティ
  • Windowsのセキュリティ
  • 書式指定文字列の構文チェック。printfで不適切なフィールド文字を使用しているかどうかなど。

話は少し変わるが、例えばC++でこのようなセキュアなCRT関数を使用したい場合は、_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMESを1として定義すると、例えばstrcpyの呼び出しがバッファーオーバーランを防ぐstrcpy_sの呼び出しに変更される。つまりセキュリティで保護されたテンプレートでオーバーロードすることが可能となる。

fopen_s

プロトタイプ

errno_t fopen_s(FILE * restrict * restrict streamptr, const char * restrict filename, const char * restrict mode);

返り値

正常に終了すると0, そうでないと0でない値を返す。

使い方

/* Program to create backup of a file */

#include <stdio.h>

int main(void)
{
  FILE *in, *out;
  in = new FILE;
  if (fopen_s(&in, "TESTFILE.DAT", "rt")){
    fprintf(stderr, "Cannot open input file.\n");
    return 1;
  }
  if (fopen_s(&out, "TESTFILE.BAK", "wt")){
    fprintf(stderr, "Cannot open output file.\n");
    return 1;
  }
  while (!feof(in)){
    fputc(fgetc(in), out);
  }
  fclose(in);
  fclose(out);
  return 0;
}

fprintf_s

fprintfより優れている点

プロトタイプ

int fprintf_s(FILE * restrict stream, const char * restrict format, [,argument, ...]);

int fwprintf_s(FILE * restrict stream, const wchar_t * restrict format, [,argument, ...]);

返り値

書き込まれた文字数を返す。エラーが発生した場合は負の値を返す。

使い方

#include <stdio.h>
int main(void)
{
  FILE *stream;
  int i = 100;
  char c = 'C';
  float f = 1.234;
  /* Open a file for update */
  if(fopen_s(&stream,"DUMMY.FIL", "w+")){
    printf("Unable to create DUMMY.FIL");
  }
  else{
    /* Write some data to the file */
    fprintf_s(stream, "%d %c %f", i, c, f);
  }
  /* Close the file */
  fclose(stream);
  return 0;
}

参考