メインコンテンツ

CWE Rule 676

Use of Potentially Dangerous Function

R2023a 以降

説明

ルールの説明

The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.

Polyspace 実装

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

  • 不正確な文字列形式指定子によるバッファー オーバーフロー

  • 標準ライブラリ関数呼び出しでデータ レースが発生しました

  • 文字列操作で格納先バッファーがオーバーフローしています

  • システム関数の安全でない呼び出し

  • 文字列から数値への変換は安全ではありません

  • 危険な標準関数を使用しています

  • 疑似乱数を生成するための rand() の使用

すべて展開する

問題

この問題は、sscanf などの関数の書式指定子引数が、メモリ バッファー引数でのオーバーフローまたはアンダーフローにつながる場合に発生します。

リスク

形式指定子で指定した精度がメモリ バッファー サイズより大きいと、オーバーフローが発生します。オーバーフローは、メモリ破損のような予期しない動作を引き起こす可能性があります。

修正方法

メモリ バッファー サイズに適合する形式指定子を使用します。

例 — メモリ バッファー オーバーフロー
#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%33c", buf);  //Noncompliant
}

この例では、bufchar 要素を 32 個格納できます。したがって、形式指定子 %33c はバッファー オーバーフローの原因となります。

修正 — より低い精度を形式指定子に使用

考えられる修正の 1 つは、バッファーに読み込む要素の数を減らすことです。

#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%32c", buf);
}
問題

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください。

この問題は、以下の場合に発生します。

  1. 複数のタスクから同じ標準ライブラリ関数を呼び出す。

    たとえば、複数のタスクから strerror 関数を呼び出します。

  2. 呼び出しが共通の保護を使って保護されていない。

    たとえば、呼び出しが同じクリティカル セクションによって保護されていません。

この欠陥によってフラグが付けられた関数は、再呼び出し可能であることは保証されません。前の呼び出しが実行を完了する前に、割り込まれたり、安全に再度呼び出されることができる場合、関数は再呼び出し可能です。関数が再呼び出し可能ではない場合、複数のタスクからその関数を保護なしで呼び出すと、同時実行の問題の原因となる可能性があります。フラグが付けられた関数のリストについては、次を参照してください。CON33-C:Avoid race conditions when using library functions.

この欠陥を検出するには、解析前にマルチタスキング オプションを指定しなくてはなりません。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細については、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

この欠陥によってフラグが付けられた関数は、その実装でグローバル変数や静的変数が使用される可能性があるため、再呼び出し可能ではありません。複数のタスクから保護なしでその関数を呼び出すと、一方のタスクからの関数呼び出しが別のタスクからの呼び出しに干渉する可能性があります。その関数の 2 つの呼び出しがグローバル変数や静的変数に同時にアクセスし、予期しない結果が生じる可能性があります。

この呼び出しが、異常終了、サービス拒否攻撃、データの整合性違反など、より深刻なセキュリティの脆弱性の原因となることもあります。

修正方法

この欠陥を修正するには、以下のいずれかを行います。

  • 標準ライブラリ関数の再呼び出し可能なバージョンが存在する場合はそちらを使用します。

    たとえば、strerror() ではなく、strerror_r() または strerror_s() を使用します。この欠陥によってフラグが付けられた関数の代替方法については、「CON33-C」を参照してください。

  • 共通のクリティカル セクションまたは時間的に排他を使用して、その関数呼び出しを保護します。

    クリティカル セクション詳細 (-critical-section-begin -critical-section-end)および時間的に排他なタスク (-temporal-exclusions-file)を参照してください。

    再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。以下の例を参照してください。

例 — 標準ライブラリ関数に対する複数のタスクからの保護されない呼び出し

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);  //Noncompliant
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

この例では、マルチタスクの動作をエミュレートするため、以下のオプションを指定しなければなりません。

オプション仕様
マルチタスクを手動で構成
タスク (-entry-points)

task1

task2

task3

クリティカル セクション詳細 (-critical-section-begin -critical-section-end)開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例では、タスク task1task2 および task3 が関数 func を呼び出します。関数 func は再呼び出し可能ではない標準ライブラリ関数 strerror を呼び出します。

task3 はクリティカル セクションの内部で func を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。

これらの 3 つのタスクは、共通の保護を使用しないで再呼び出し可能ではない標準ライブラリ関数を呼び出しています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

アイコンをクリックすると、標準ライブラリ関数呼び出しへのエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる呼び出しがクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_sectionend_critical_section が表示されます。

修正 - 標準ライブラリ関数の再呼び出し可能なバージョンを使用

考えられる 1 つの修正方法として、標準ライブラリ関数 strerror の再呼び出し可能なバージョンを使用します。POSIX® バージョンの strerror_r を使用できます。これには同じ機能がありますが、スレッド セーフ性が保証されます。


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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);
enum { BUFFERSIZE = 64 };

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char errmsg[BUFFERSIZE];
    if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) {
      /* Handle error */
    }
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}
修正 — クリティカル セクション内に関数呼び出しを配置

考えられる 1 つの修正方法として、strerror の呼び出しをクリティカル セクション内に配置します。クリティカル セクションは複数の方法で実装できます。

たとえば、中間関数 func の呼び出しを 3 つのタスクの同じクリティカル セクション内に配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。func の呼び出し、つまり 3 つのタスクからの strerror の呼び出しは互いに干渉する可能性はありません。

クリティカル セクションを実装するには、3 つのそれぞれのタスク内で begin_critical_sectionend_critical_section の呼び出しの間に func を呼び出します。


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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    begin_critical_section();
    func(fptr1);
    end_critical_section();
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     begin_critical_section();
     func(fptr2);
     end_critical_section();
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

修正 — タスクを時間的に排他にする

別の修正方法として、タスク task1task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。

[構成] ペインで、以下の追加オプションを指定します。

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder
     -temporal-exclusions-file "C:\exclusions_file.txt"
ここで、ファイル C:\exclusions_file.txt には以下の行があります。
task1 task2 task3

問題

この問題は、特定の文字列操作関数によって、その格納先バッファー引数にバッファー サイズを超えるオフセットで書き込まれた場合に発生します。

たとえば、関数 sprintf(char* buffer, const char* format) を呼び出す際に、buffer より大きいサイズの定数文字列 format を使用する場合などです。

リスク

バッファー オーバーフローにより、メモリ破損やシステム停止といった予期しない動作を引き起こす可能性があります。また、バッファー オーバーフローは、コード インジェクションのリスクにもつながります。

修正方法

1 つの解決策として、代替となる関数を使用して、書き込まれる文字の数を制限します。次に例を示します。

  • 書式設定されたデータを文字列に書き込むのに sprintf を使用している場合は、代わりに snprintf_snprintf または sprintf_s を使用して長さを制御します。あるいは、asprintf を使用して、格納先バッファーに必要なメモリを自動で割り当てます。

  • 書式設定されたデータを可変引数リストから文字列に書き込むのに vsprintf を使用している場合は、代わりに vsnprintf または vsprintf_s を使用して長さを制御します。

  • ワイド文字列をコピーするのに wcscpy を使用している場合は、代わりに wcsncpywcslcpy または wcscpy_s を使用して長さを制御します。

別の解決策として、バッファー サイズを増やします。

例 — sprintf の使用におけるバッファー オーバーフロー
#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    sprintf(buffer, fmt_string);  //Noncompliant
}

この例では、bufferchar 要素を 20 個格納できますが、fmt_string はより大きいサイズとなっています。

修正 — snprintfsprintf の代わりに使用

1 つの修正方法として、関数 snprintf を使用して長さを制御します。

#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    snprintf(buffer, 20, fmt_string);
}
問題

この問題は、処理系定義のコマンド プロセッサを呼び出す関数を使用した場合に発生します。これらの関数には以下が含まれます。

  • C 標準関数 system()

  • POSIX 関数 popen()

  • Windows® 関数 _popen() および _wpopen()

リスク

コマンド プロセッサを呼び出す関数の引数がサニタイズされていない場合、悪用可能な脆弱性の原因になる可能性があります。攻撃者は任意のコマンドを実行したり、システム上の任意の場所にあるデータを読み取ったり変更したりできます。

修正方法

コマンド プロセッサを呼び出す system ファミリ関数を使用しないようにします。代わりに、POSIX の execve() や WinAPI の CreateProcess() など、より安全な関数を使用します。

例 — system() の呼び出し
# include <string.h>
# include <stdlib.h>
# include <stdio.h>
# include <unistd.h>

enum { 
SIZE512=512,
SIZE3=3};

void func(char *arg)
{
	char buf[SIZE512];
	int retval=snprintf(buf, sizeof(buf), "/usr/bin/any_cmd %s", arg);
	
	if (retval<=0 || retval>SIZE512){
		/* Handle error */
		abort();
	}
	/* Use of system() to pass any_cmd with 
	unsanitized argument to command processor */
	
	if (system(buf) == -1) {  //Noncompliant
	/* Handle error */
  }
} 

この例では、実行するコマンド プロセッサのホスト環境に system() が引数を渡しています。このコードはコマンド インジェクションによる攻撃に対して脆弱です。

修正 — 引数をサニタイズして execve() を使用

次のコードでは、any_cmd の引数がサニタイズされた後に execve() に渡されて実行されます。exec ファミリ関数は、コマンド インジェクション攻撃に対して脆弱ではありません。

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

enum { 
SIZE512=512,
SIZE3=3};


void func(char *arg)
{
  char *const args[SIZE3] = {"any_cmd", arg, NULL};
  char  *const env[] = {NULL}; 
  
  /* Sanitize argument */
  
  /* Use execve() to execute any_cmd. */

  if (execve("/usr/bin/time", args, env) == -1) { 
    /* Handle error */
  }
} 
問題

この問題は、文字列から整数または浮動小数点値への変換が行われ、その変換メソッドにロバストなエラー処理が含まれていなかった場合に発生します。

リスク

文字列を数値に変換すると、データの損失や誤った解釈の原因となる場合があります。変換の検証またはエラー処理がないと、プロクラムは無効な値を使って続行されます。

修正方法
  • 数値を検証する新たなチェックを追加します。

  • strtolstrtollstrtoulstrtoull などの、文字列から数値へのよりロバストな変換関数を使用します。

例 — atoi による変換
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int demo_check_string_not_empty(char *s)
{
    if (s != NULL)
        return strlen(s) > 0; /* check string null-terminated and not empty */
    else
        return 0;
}

int unsafestrtonumeric(char* argv1)
{
    int s = 0;
    if (demo_check_string_not_empty(argv1))
    {
        s = atoi(argv1);  //Noncompliant
    }
    return s;
}

この例では、atoi を使って argv1 を整数に変換しています。atoi では無効な整数の文字列についてのエラーが提供されません。この変換は予期せず失敗する可能性があります。

修正 — 代わりに strtol を使用

考えられる 1 つの修正方法として、strtol を使用して入力文字列と変換後の整数を検証します。

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

static int demo_check_string_not_empty(char *s)
{
    if (s != NULL)
        return strlen(s) > 0; /* check string null-terminated and not empty */
    else
        return 0;
}

int unsafestrtonumeric(char *argv1)
{
    char *c_str = argv1;
    char *end;
    long sl;

    if (demo_check_string_not_empty(c_str))
    {
        errno = 0; /* set errno for error check */
        sl = strtol(c_str, &end, 10);
        if (end == c_str)
        {
            (void)fprintf(stderr, "%s: not a decimal number\n", c_str);
        }
        else if ('\0' != *end)
        {
            (void)fprintf(stderr, "%s: extra characters: %s\n", c_str, end);
        }
        else if ((LONG_MIN == sl || LONG_MAX == sl) && ERANGE == errno)
        {
            (void)fprintf(stderr, "%s out of range of type long\n", c_str);
        }
        else if (sl > INT_MAX)
        {
            (void)fprintf(stderr, "%ld greater than INT_MAX\n", sl);
        }
        else if (sl < INT_MIN)
        {
            (void)fprintf(stderr, "%ld less than INT_MIN\n", sl);
        }
        else
        {
            return (int)sl;
        }
    }
    return 0;
}
問題

この問題は、コードで、バッファー オーバーフローになる可能性があるやり方でデータをバッファーに書き込む標準関数が使用されている場合に発生します。

次の表に、危険な標準関数、各関数を使用した場合のリスク、および代用する関数を示します。チェッカーは以下にフラグを設定します。

  • 本質的に危険な関数の使用。

  • コンパイル時にデータが書き込まれるバッファーのサイズを決定可能な場合にのみ、危険な可能性のある関数を使用する。チェッカーは、バッファーが動的に割り当てられるこのような関数の使用にフラグを設定しません。

危険な関数リスク レベルより安全な関数
gets本質的に危険 — コンソールからは入力の長さを制御できない。fgets
std::cin::operator>> および std::wcin::operator>>本質的に危険 — コンソールからは入力の長さを制御できない。

入力長を制御するには、cin 呼び出しの前に cin.width を置きます。このメソッドは入力を切り捨てる結果になる場合があります。

バッファー オーバーフローと入力切り捨てが発生しないようにするには、>> 演算子の格納先に std::string オブジェクトを使用します。

strcpy潜在的に危険 — 格納先バッファーのサイズが小さすぎて、ソース バッファーと null 終端が収まらない場合は、バッファー オーバーフローが発生する可能性がある。 関数 strlen() を使用してソース バッファーのサイズを決定し、格納先バッファーがソース バッファーと null 終端を収められるように十分なメモリを割り当てます。strcpy の代わりに、関数 strncpy を使用します。
stpcpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。stpncpy
lstrcpy または StrCpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。StringCbCopyStringCchCopystrncpystrcpy_s または strlcpy
strcat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。strncatstrlcat または strcat_s
lstrcat または StrCat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。StringCbCatStringCchCatstrncaystrcat_s または strlcat
wcpcpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。wcpncpy
wcscat潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。wcsncatwcslcat または wcncat_s
wcscpy潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。wcsncpy
sprintf潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。snprintf
vsprintf潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。vsnprintf
リスク

これらの関数はバッファー オーバーフローの原因となることがあり、攻撃者はこれを利用してプログラムに侵入できます。

修正方法

修正方法は欠陥の根本原因によって異なります。上の表に記載されている修正と以下の修正付きのコード例を参照してください。

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

例 — sprintf の使用
#include <stdio.h>
#include <string.h>
#include <iostream>

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (sprintf(dst, "%s", str) == 1)  //Noncompliant
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}

この関数例では、sprintf を使用して文字列 strdst にコピーしています。しかし、str がバッファーより大きいと、sprintf がバッファー オーバーフローの原因となる場合があります。

修正 — バッファー サイズを指定して snprintf を使用

1 つの修正方法として、snprintf を代わりに使用し、バッファー サイズを指定します。

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

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (snprintf(dst, sizeof(dst), "%s", str) == 1)
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}
問題

この問題は、疑似乱数を生成するために関数 rand を使用した場合に発生します。

リスク

関数 rand は、暗号法的に脆弱です。つまり、rand によって生成される数は予測される可能性があります。rand から生成された疑似乱数をセキュリティの目的に使用しないでください。予測可能な乱数値で実行フローを制御する場合は、プログラムが攻撃に対して脆弱になります。

修正方法

CryptGenRandom (Windows)、OpenSSL/RAND_bytes(Linux/UNIX)、random (POSIX) などの暗号法的に堅牢な疑似乱数を使用します。

例 — ランダムなループ回数

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

volatile int rd = 1;
int main(int argc, char *argv[])
{   
	int j, r, nloops;
	struct random_data buf;
	int i = 0;
	
	nloops = rand(); //Noncompliant
	
	for (j = 0; j < nloops; j++) {
		i = rand(); //Noncompliant
		printf("random_r: %ld\n", (long)i);
	}
	return 0;
}

この例では、rand を使用して、乱数の nloopsi を生成します。これらの変数の予測可能性によって、これらの関数が攻撃に対して脆弱になります。

修正 — より強固な PRNG を使用

1 つの修正方法として、脆弱な PRNG を、より強固な乱数発生器に置き換えます。たとえば、このコードでは POSIX ライブラリの PRNG random() が使用されています。random は、呼び出されるたびに異なる数でシード値が生成されるため、非常に強力な PRNG です。


  
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TIME_UTC 1
volatile int rd = 1;
int randomWrapper(){
	struct timespec ts;
  if (timespec_get(&ts, TIME_UTC) == 0) {
    /* Handle error */
  }
  srandom(ts.tv_nsec ^ ts.tv_sec);  /* Seed the PRNG */
	return random();
}
int main(int argc, char *argv[])
{   
    int j, r, nloops;
    struct random_data buf;
    int i = 0;
    
    nloops = randomWrapper();
    
    for (j = 0; j < nloops; j++) {
		i = randomWrapper();
        printf("random_r: %ld\n", (long)i);
    }
    return 0;
}

チェック情報

カテゴリ: API / Function Errors

バージョン履歴

R2023a で導入