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

STR32-C. 文字列を引数にとるライブラリ関数に null 終端されていない文字配列を渡さない

STR32-C. 文字列を引数にとるライブラリ関数に null 終端されていない文字配列を渡さない

多くのライブラリ関数は、文字列が適切に null 終端されているという前提で、文字列やワイド文字列を引数に取る。null 終端されていない文字配列をこれらの関数に渡すと、オブジェクトの境界外のメモリにアクセスしてしまう可能性がある。文字列もしくはワイド文字列を引数に取るライブラリ関数に、null 終端されていない文字配列を渡してはならない。

違反コード

下記のコード例はルールに違反している。理由は、printf() に引数として渡される文字配列 c_str が null 終端されていないからである。(文字配列の適切な初期化方法は「STR11-C. 文字列リテラルで初期化される文字配列のサイズを指定しない」を参照)。

#include <stdio.h>

void func(void) {
  char c_str[3] = "abc";
  printf("%s\n", c_str);
}
適合コード

下記の適合コードは配列宣言で文字配列のサイズを指定していない。配列のサイズを省略すると、コンパイラは、null 終端文字を含め文字列リテラルを格納するために十分なサイズのメモリ領域を割り当てる。

#include <stdio.h>

void func(void) {
  char c_str[] = "abc";
  printf("%s\n", c_str);
}
違反コード

下記のコード例は本ルールに違反している。wcslen() に渡されるワイド文字列 cur_msg が null 終端されていないからである。

#include <stdlib.h>
#include <wchar.h>

wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;

void lessen_memory_usage(void) {
  wchar_t *temp;
  size_t temp_size;

  /* ... */

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
    /* temp と cur_msg は null 終端されていないかもしれない */
    if (temp == NULL) {
      /* エラー処理 */
    }

    cur_msg = temp;
    cur_msg_size = temp_size;
    cur_msg_len = wcslen(cur_msg);
  }
}
適合コード

この適合コードでは、cur_msgwcslen() に渡されるときに必ず null 終端されている。

#include <stdlib.h>
#include <wchar.h>

wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;

void lessen_memory_usage(void) {
  wchar_t *temp;
  size_t temp_size;

  /* ... */

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
    /* temp と cur_msg は null 終端されていないかもしれない */
    if (temp == NULL) {
      /* エラー処理 */
    }

    cur_msg = temp;
    /* cur_msg を適切に null 終端する */
    cur_msg[temp_size - 1] = L'\0';
    cur_msg_size = temp_size;
    cur_msg_len = wcslen(cur_msg);
  }
}
違反コード (strncpy())

strncpy() 関数は文字列を入力としてとるが、処理結果が null 終端されることは保証しない。下記の違反コード例では、コピー元配列の最初の n 文字に null 文字が含まれていない場合、コピー先の文字列も null 終端されない。null 終端されていない文字列を strlen() に渡したときの動作は未定義である。

#include <string.h>

enum { STR_SIZE = 32 };

size_t func(const char *source) {
  char c_str[STR_SIZE];
  size_t ret = 0;

  if (source) {
    c_str[sizeof(c_str) - 1] = '\0';
    strncpy(c_str, source, sizeof(c_str));
    ret = strlen(c_str);
  } else {
    /* Handle null pointer */
  }
  return ret;
}
適合コード (切り捨て)

プログラマが文字列の切り捨てを意図しているのであれば、下記の適合コードは正しい。

#include <string.h>

enum { STR_SIZE = 32 };

size_t func(const char *source) {
  char c_str[STR_SIZE];
  size_t ret = 0;

  if (source) {
    strncpy(c_str, source, sizeof(c_str) - 1);
    c_str[sizeof(c_str) - 1] = '\0';
    ret = strlen(c_str);
  } else {
    /* Handle null pointer */
  }
  return ret;
}
適合コード (切り捨て, strncpy_s())

C言語規格、附属書 K の strncpy_s() 関数を使っても文字列の切り捨てコピーを行うことができる。strncpy_s() 関数はコピー元配列からコピー先配列へ最大n文字コピーする。コピー元配列から null 文字がコピーされない場合、コピー先配列のn番目が null 文字に設定され、結果として得られる文字列が null 終端されることが保証される。

#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

enum { STR_SIZE = 32 };

size_t func(const char *source) {
  char c_str[STR_SIZE];
  size_t ret = 0;

  if (source) {
    errno_t err = strncpy_s(
      c_str, sizeof(c_str), source, strnlen(source, sizeof(c_str))
    );
    if (err != 0) {
      /* エラー処理 */
    } else {
      ret = strnlen(c_str, sizeof(c_str));
    }
  } else {
     /* null ポインタの処理 */
  }
  return ret;
}
適合コード (切り捨てずにコピー)

文字列を切り捨てずにコピーすることを意図している場合、この適合コードではデータをコピーし、コピー先配列が null 終端されることを保証している。文字列をコピーできないときはエラーとして処理される。

#include <string.h>

enum { STR_SIZE = 32 };

size_t func(const char *source) {
  char c_str[STR_SIZE];
  size_t ret = 0;

  if (source) {
    if (strnlen(source, sizeof(c_str)) < sizeof(c_str)) {
      strcpy(c_str, source);
      ret = strlen(c_str);
    } else {
      /* コピー元の文字列が長過ぎる場合の処理 */
    }
  } else {
    /* null ポインタの処理 */
  }
  return ret;
}

このコードが完全ではないことに注意。source が NULL の場合、有効な文字列の場合、null 終端されていないが最初の 32 バイトが有効な場合は、適切に処理される。しかし、source が NULL ではないが無効なメモリを指している場合や、最初の 32 バイトのいずれかが無効なメモリである場合、最初の strnlen() 呼び出しがその無効なメモリにアクセスしてしまい、その結果は未定義の動作となる。残念ながら、C 標準では、source が指しているメモリに関する外部的な情報なしに、このような状態を防いだり検出したりする手段はない。

リスク評価

文字列を受け取るライブラリ関数に渡す文字列を適切に null 終端しないと、バッファオーバーフローが発生したり、脆弱なプロセスの権限で任意のコードを実行される可能性がある。null 終端エラーは、意図しない情報開示につながる可能性もある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

STR32-C

P12

L1

自動検出(最新の情報はこちら

ツール

バージョン

チェッカー

説明

Astrée
23.04

Supported

Astrée supports the implementation of library stubs to fully verify this guideline.

Axivion Bauhaus Suite

7.2.0

CertC-STR32 Partially implemented: can detect some violation of the rule
CodeSonar
7.3p0
MISC.MEM.NTERM.CSTRING Unterminated C String
Compass/ROSE



このルールの違反を検出できる。

Coverity
2017.07
STRING_NULL 実装済み
Helix QAC

2023.1

DF2835, DF2836, DF2839


Klocwork
2023.1

NNTS.MIGHT
NNTS.MUST
SV.STRBO.BOUND_COPY.UNTERM


LDRA tool suite
9.7.1

404 S, 600 S

Partially implemented

Parasoft C/C++test
2022.2
CERT_C-STR32-a

Avoid overflow due to reading a not zero terminated string

Polyspace Bug Finder

R2023a

CERT C: Rule STR32-C


Checks for:

  • Invalid use of standard library string routine
  • Tainted NULL or non-null-terminated string

Rule partially covered.

PVS-Studio

7.25

V692
TrustInSoft Analyzer

1.38

match format and arguments Partially verified.
関連するガイドライン
CERT C++ Secure Coding Standard STR32-CPP. Null-terminate character arrays as required
ISO/IEC TR 24772:2013 String Termination [CMJ]
ISO/IEC TS 17961:2013 Passing a non-null-terminated character sequence to a library function that expects a string [strmod]
MITRE CWE CWE-119, Improper Restriction of Operations with the Bounds of a Memory Buffer
CWE-170, Improper null termination
参考資料
[Seacord 2013] Chapter 2, "Strings"
[Viega 2005] Section 5.2.14, "Miscalculated NULL Termination"
翻訳元

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

STR32-C. Do not pass a non-null-terminated character sequence to a library function that expects a string

Top へ

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