第1回 バッファサイズの指定

セキュアなプログラミングのため、strcpy ではなく、バッファのサイズを指定できる strncpy を使うというのは、もはや常識といえるでしょう。
ところが、strncpy は、バッファサイズが足りない場合には、終端にヌル文字を付加しません。
そのため、strcpy のように、strncpy 自体がバッファオーバーフローを引き起こすことはありませんが、 strncpy を呼び出した側でバッファオーバーフローが発生する可能性は依然として残っています。

strncpy の結果を安全に利用するには、最低限、下記のコード例のように strncpy の呼び出し側でバッファをヌル文字で終端させてやる必要があります。

  char buf[ BUFSIZE ];

  buf[ sizeof( buf ) - 1 ] = 0;     /* terminate */
  strncpy( buf, s, sizeof( buf ) - 1 );

そこで、より安全な "my_strncpy()" を実装することを考えてみましょう。

  char *my_strncpy( char *s, const char *t, size_t n );

自明のとおり、文字列 t を s にコピーする関数です。n に指定されるのは、以下の a)、b) のいずれかとなります。

  • a) strncpy と同じく、コピーされる最大文字数を指定する

n に指定されているのは、コピーされる文字の数ですから、ヌル文字を含めるとバッファには n + 1 バイトのサイズが必要となります。
具体的な実装はこのような感じになります。

char *my_strncpy( char *s, const char *t, size_t n )
{
    char *s0 = s;

    if( s == NULL ) return s;
    if( t == NULL ) t = "";
    while( *t && n ){
        *s++ = *t++;
        n--;
    }
    *s = '\0';
    return s0;
}

この場合の呼び出し側の典型的な例は次のようになります。

  char buf[ BUFSIZE ];
  
  my_strncpy( buf, s, sizeof( buf ) - 1 );
  printf( "%s\n", buf );  /* buf is terminated */
  • b) バッファのサイズを指定する

n に指定されているのは、バッファ自体のサイズですので、文字列としては最大 n - 1 文字を格納することが可能となります。
具体的な関数の実装は、以下のようになります。

char *my_strncpy( char *s, const char *t, size_t n )
{
    char *s0 = s;
    
    if( n == 0 || s == NULL ) return s;
    if( t == NULL ) t = "";
    n--;
    while( *t && n ){
        *s++ = *t++;
        n--;
    }
    *s = '\0';
    return s0;
}

この場合の呼び出し側の典型的な例は、次のようになります。

  char buf[ BUFSIZE ];

  my_strncpy( buf, s, sizeof( buf ) );
  printf( "%s\n", buf );  /* buf is terminated */

a) と b) の違いがわかるでしょうか。

a) の場合に呼び出し側でバッファサイズの -1 を忘れた場合、バッファを越えてヌル文字が書き込まれてしまいます。
この典型的な1バイトのバッファオーバーフローは、off-by-one エラーと呼ばれ、セキュリティ上の問題を引き起こすことがあります。
関数を呼び出す側が -1 を忘れる可能性を考えるのであれば、b) のほうがより安全といえます。

この a)とb) 2種類を実装した実際の好例が Apache と Samba です。
Apache では apr_cpystrn という名前で、上記 b) の形式の関数を定義しています。
また、Samba では safe_strcpy という名前で上記 a) の形式の関数を定義していますが、これまで何度も off-by-one を引き起こしています。
Samba での実装は、strncpy に比べれば、確かに Safe ではありますが、呼び出す側で気を配ってやる必要があるというのは、セキュリティ上好ましい実装であるとはいえません。

コーディングの際には、自分の書くコード範囲、今回の例では my_strncpy、だけの安全に気を配るのではなく、自分の書いたコードを利用する側の安全にも気を配ってこそ、全体のセキュリティを保つことができるのではないでしょうか。
さて、実は上の my_strncpy にはまだ、セキュリティホールを引き起こす可能性のある仕様上の問題があります。次回はその問題点について説明します。