メインコンテンツ

CWE Rule 170

Improper Null Termination

R2023a 以降

説明

ルールの説明

The software does not terminate or incorrectly terminates a string or array with a null character or equivalent terminator.

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • 標準ライブラリ文字列ルーチンの無効な使用

  • string 配列での null 値の欠落

  • readlink() の不適切な使用

  • 汚染された NULL 文字列または非 NULL 終端文字列

すべて展開する

問題

この問題は、文字列ライブラリ関数が無効な引数で呼び出された場合に発生します。

リスク

リスクは無効な引数のタイプによって異なります。たとえば、コピー先引数より大きいコピー元引数を指定して関数 strcpy を使用すると、バッファー オーバーフローが発生する可能性があります。

修正方法

修正方法は欠陥に関連する標準ライブラリ関数に依存します。場合によっては、関数呼び出しの前に関数の引数を制約することができます。たとえば、次の関数 strcpy を考えます。

char * strcpy(char * destination, const char* source)
これが、使用可能なバッファーに比べて大きすぎるバイト数をコピー先引数にコピーしようとする場合、strcpy を呼び出す前にコピー元引数を制約します。場合によっては、代替となる関数を使用してエラーを回避できます。たとえば、strcpy の代わりに strncpy を使用するとコピーされるバイト数を制御できます。

以下の修正例を参照してください。

問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 標準ライブラリ文字列ルーチンの無効な使用によるエラー
 #include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  char gbuffer[5],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);  //Noncompliant
  /* Error: Size of text is less than gbuffer */

  return(res);
 }

文字列 text はサイズが gbuffer より大きくなっています。したがって、関数 strcpytextgbuffer にコピーできません。

修正 — 有効な引数を使用

1 つの修正方法として、コピー先の文字列 gbuffer をソース文字列 text 以上のサイズで宣言するとします。

#include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  /*Fix: gbuffer has equal or larger size than text */
  char gbuffer[20],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);

  return(res);
 }
問題

この問題は、null 文字 '\0' で終了するだけの十分なスペースが文字列にない場合に発生します。

この欠陥は C のプロジェクトのみに当てはまります。

リスク

暗黙的な NULL 終端を仮定せずに文字列を配列にコピーすると、バッファー オーバーフローが発生する可能性があります。

修正方法

文字配列をリテラルで初期化する場合、配列範囲の指定を避けます。

char three[]  = "THREE";
コンパイラが自動的に NULL 終端の領域を割り当てます。前述の例では、コンパイラは 5 つの文字と NULL 終端のために十分な領域を割り当てます。

初期化後に問題が発生した場合、NULL 終端を考慮するために配列のサイズを 1 だけ増やさなければならない場合があります。

特定の状況では、文字列の代わりに文字のシーケンスを使用して文字配列を初期化することが必要な場合があります。この場合、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。

例 — 配列のサイズが小さすぎる
void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[5] = "THREE"; //Noncompliant
}

文字配列 three のサイズは 5 で、この配列には 'T''H''R''E' および 'E' の 5 つの文字があります。three の大きさは 5 バイトしかないため、最後に null 文字を入れる余地がありません。

修正 — 配列のサイズを増加

1 つの修正方法として、配列のサイズを変更して 5 つの文字と null 文字が入るようにできます。

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[6] = "THREE";
}
修正 — 初期化の方法を変更

1 つの修正方法として、配列のサイズを空白にしたままで文字列を初期化することができます。この初期化方法では、5 つの文字と終端の null 文字のために十分なメモリが割り当てられます。

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[]  = "THREE";
}
問題

この問題は、バッファー内に NULL 終端用のスペースを残さない、readlink() へのバッファー サイズ引数を渡した場合に発生します。

次に例を示します。

ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf));
この 3 番目の引数のサイズは 2 番目の引数のサイズとまったく同じです。シンボリック リンクがバッファーを占有する場合、このように readlink() を使用すると、NULL 終端を入れる領域が残りません。

リスク

関数 readlink() は、シンボリック リンク (最初の引数) のコンテンツをバッファー (2 番目の引数) にコピーします。ただし、この関数はコピーしたコンテンツに NULL 終端を追加しません。readlink() を使用後、NULL 終端をバッファーに明示的に追加しなければなりません。

readlink の使用時にバッファー全体が占有されると、NULL 終端の領域が残りません。

修正方法

関数 readlink() を使用するときは、3 番目の引数をバッファー サイズよりも 1 文字分短くします。

その後、NULL 終端をバッファーに追加します。NULL 終端の追加場所を決めるには、readlink() の戻り値をチェックします。戻り値が -1 の場合、エラーが発生しています。そうでない場合、戻り値はコピーされた文字数 (バイト) です。

例 — readlink の不適切なサイズの引数
#include <unistd.h>

#define SIZE1024 1024

extern void display_path(const char *);

void func() {
    char buf[SIZE1024];
    ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf)); //Noncompliant
    if (len > 0) {
        buf[len - 1] = '\0';
    }
    display_path(buf);
}

この例では、readlink の 3 番目の引数がバッファー (2 番目の引数) のサイズとまったく同じです。最初の引数がバッファーを占有する場合、このように readlink を使用すると、NULL 終端の領域が残りません。

また、文字がコピーされないとき、readlink の戻り値は 0 です。以下のステートメントでは、len が 0 のときにバッファー アンダーフローになります。

buf[len - 1] = '\0';

修正 — サイズの引数をバッファー サイズよりも 1 文字分短くする

1 つの修正方法として、readlink の 3 番目の引数を 2 番目の引数のサイズよりも 1 文字分短くします。

修正した次のコードでは、readlink が 0 を返す場合も考慮します。

#include <stdlib.h>
#include <unistd.h>

#define fatal_error() abort()
#define SIZE1024 1024

extern void display_path(const char *);

void func() {
    char buf[SIZE1024];
    ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf) - 1); 
    if (len != -1) {
        buf[len] = '\0';
        display_path(buf);
    }
    else {
        /* Handle error */
        fatal_error();
    }
}
問題

この問題は、strcpysprintf などの文字列バッファーを暗黙的にデリファレンスする文字列操作ルーチンで、セキュリティで保護されていないソースからの文字列が使用された場合に発生します。

汚染された NULL 文字列または非 NULL 終端文字列では、scanf ファミリの可変個引数関数の呼び出しから返された文字列に関する欠陥が報告されません。同様に、文字列と一緒に %s 指定子を printf ファミリの可変個引数関数に渡した場合も欠陥は報告されません。

リスク

文字列がセキュリティで保護されないソースに由来している場合、攻撃者により文字列が操作されている可能性や、文字列ポインターが異なるメモリ位置に向けられている可能性があります。

文字列が NULL である場合、文字列ルーチンは文字列をデリファレンスできず、プログラムがクラッシュする原因となります。文字列が null で終了しない場合、文字列ルーチンでは文字列がいつ終了するかわからない可能性があります。このエラーは範囲外への書き込みの原因となり、バッファー オーバーフローを引き起こします。

修正方法

文字列は、使用する前に検証します。以下についてチェックします。

  • 文字列が NULL でない。

  • 文字列が null で終了している。

  • 文字列のサイズが、必要なサイズと一致している。

チェッカーの拡張

既定では、Polyspace® は外部ソースからのデータは汚染されていると仮定します。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

例 — 文字列を入力から取得
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	strncat(str, userstr, SIZE128-(strlen(str)+1));//Noncompliant //Noncompliant
	print_str(str);
}


この例では、文字列 str は引数 userstr と連結しています。userstr の値は不明です。userstr のサイズが使用可能なスペースより大きい場合、この連結はオーバーフローします。

修正 — データを検証

1 つの修正方法として、strncat で使用する前に、userstr のサイズをチェックして、文字列が必ず null で終了するようにします。この例では、補助関数 sansitize_str を使用して文字列を検証しています。欠陥はこの関数に集中しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
int sanitize_str(char* s) {
	int res = 0; 
	if (s && (strlen(s) > 0)) { // Noncompliant-TAINTED_STRING only flagged here //Noncompliant
		// - string is not null
		// - string has a positive and limited size
		// - TAINTED_STRING on strlen used as a firewall
		res = 1;
	}
	return res; 
}
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (sanitize_str(userstr))	
		strncat(str, userstr, SIZE128-(strlen(str)+1));
	print_str(str);
}
修正 — データを検証

別の修正方法として、特定の文字列を含む関数 errorMsg および warningMsg を呼び出します。

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

#define SIZE128 128

extern void print_str(const char*);

void warningMsg(char* userstr)
{
    char str[SIZE128] = "Warning: ";
    strncat(str, userstr, SIZE128-(strlen(str)+1));
    print_str(str);
}

void errorMsg(char* userstr)
{
  char str[SIZE128] = "Error: ";
  strncat(str, userstr, SIZE128-(strlen(str)+1));
  print_str(str);
}

int manageSensorValue(int sensorValue) {
  int ret = sensorValue;
  if ( sensorValue < 0 ) {
    errorMsg("sensor value should be positive");
    exit(1);
  } else if ( sensorValue > 50 ) {
    warningMsg("sensor value greater than 50 (applying threshold)...");
    sensorValue = 50;
  }
  
  return sensorValue;
}

チェック情報

カテゴリ: Data Neutralization Issues

バージョン履歴

R2023a で導入