CWE Rule 676
説明
ルールの説明
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
}この例では、buf に char 要素を 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)を参照してください。
この問題は、以下の場合に発生します。
複数のタスクから同じ標準ライブラリ関数を呼び出す。
たとえば、複数のタスクから
strerror関数を呼び出します。呼び出しが共通の保護を使って保護されていない。
たとえば、呼び出しが同じクリティカル セクションによって保護されていません。
この欠陥によってフラグが付けられた関数は、再呼び出し可能であることは保証されません。前の呼び出しが実行を完了する前に、割り込まれたり、安全に再度呼び出されることができる場合、関数は再呼び出し可能です。関数が再呼び出し可能ではない場合、複数のタスクからその関数を保護なしで呼び出すと、同時実行の問題の原因となる可能性があります。フラグが付けられた関数のリストについては、次を参照してください。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) |
| |
クリティカル セクション詳細 (-critical-section-begin -critical-section-end) | 開始ルーチン | 終了ルーチン |
begin_critical_section | end_critical_section | |
コマンド ラインでは以下を使用できます。
polyspace-bug-finder
-entry-points task1,task2,task3
-critical-section-begin begin_critical_section:cs1
-critical-section-end end_critical_section:cs1この例では、タスク task1、task2 および task3 が関数 func を呼び出します。関数 func は再呼び出し可能ではない標準ライブラリ関数 strerror を呼び出します。
task3 はクリティカル セクションの内部で func を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。
これらの 3 つのタスクは、共通の保護を使用しないで再呼び出し可能ではない標準ライブラリ関数を呼び出しています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

アイコンをクリックすると、標準ライブラリ関数呼び出しへのエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる呼び出しがクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_section と end_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_section と end_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();
}
別の修正方法として、タスク task1、task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。
[構成] ペインで、以下の追加オプションを指定します。
| オプション | 値 |
|---|---|
時間的に排他なタスク (-temporal-exclusions-file) |
|
コマンド ラインでは以下を使用できます。
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を使用している場合は、代わりにwcsncpy、wcslcpyまたはwcscpy_sを使用して長さを制御します。
別の解決策として、バッファー サイズを増やします。
#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
}この例では、buffer は char 要素を 20 個格納できますが、fmt_string はより大きいサイズとなっています。
snprintf を sprintf の代わりに使用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() など、より安全な関数を使用します。
# 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 */
}
} この問題は、文字列から整数または浮動小数点値への変換が行われ、その変換メソッドにロバストなエラー処理が含まれていなかった場合に発生します。
文字列を数値に変換すると、データの損失や誤った解釈の原因となる場合があります。変換の検証またはエラー処理がないと、プロクラムは無効な値を使って続行されます。
数値を検証する新たなチェックを追加します。
strtol、strtoll、strtoul、strtoullなどの、文字列から数値へのよりロバストな変換関数を使用します。
#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>> | 本質的に危険 — コンソールからは入力の長さを制御できない。 | 入力長を制御するには、 バッファー オーバーフローと入力切り捨てが発生しないようにするには、 |
strcpy | 潜在的に危険 — 格納先バッファーのサイズが小さすぎて、ソース バッファーと null 終端が収まらない場合は、バッファー オーバーフローが発生する可能性がある。 | 関数 strlen() を使用してソース バッファーのサイズを決定し、格納先バッファーがソース バッファーと null 終端を収められるように十分なメモリを割り当てます。strcpy の代わりに、関数 strncpy を使用します。 |
stpcpy | 潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。 | stpncpy |
lstrcpy または StrCpy | 潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。 | StringCbCopy、StringCchCopy、strncpy、strcpy_s または strlcpy |
strcat | 潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。 | strncat、strlcat または strcat_s |
lstrcat または StrCat | 潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。 | StringCbCat、StringCchCat、strncay、strcat_s または strlcat |
wcpcpy | 潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。 | wcpncpy |
wcscat | 潜在的に危険 — 連結された結果が作成先より大きいと、バッファー オーバーフローが発生する可能性がある。 | wcsncat、wcslcat または wcncat_s |
wcscpy | 潜在的に危険 — ソースの長さがコピー先より大きいと、バッファー オーバーフローが発生する可能性がある。 | wcsncpy |
sprintf | 潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。 | snprintf |
vsprintf | 潜在的に危険 — 出力の長さが不明な長さや値に依存していると、バッファー オーバーフローが発生する可能性がある。 | vsnprintf |
これらの関数はバッファー オーバーフローの原因となることがあり、攻撃者はこれを利用してプログラムに侵入できます。
修正方法は欠陥の根本原因によって異なります。上の表に記載されている修正と以下の修正付きのコード例を参照してください。
問題を修正しない場合は、改めてレビューされないように結果またはコードにコメントを追加します。詳細は、以下を参照してください。
Polyspace ユーザー インターフェイスでのバグ修正または正当化による結果への対処 (Polyspace ユーザー インターフェイスで結果をレビューする場合)。
Polyspace Access でのバグ修正または正当化による結果への対処 (Polyspace Access) (Web ブラウザーで結果をレビューする場合)。
コードへの注釈付けと既知の結果または許容可能な結果の非表示 (IDE で結果をレビューする場合)
#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 を使用して文字列 str を dst にコピーしています。しかし、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 を使用して、乱数の nloops と i を生成します。これらの変数の予測可能性によって、これらの関数が攻撃に対して脆弱になります。
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 で導入
MATLAB Command
You clicked a link that corresponds to this MATLAB command:
Run the command by entering it in the MATLAB Command Window. Web browsers do not support MATLAB commands.
Web サイトの選択
Web サイトを選択すると、翻訳されたコンテンツにアクセスし、地域のイベントやサービスを確認できます。現在の位置情報に基づき、次のサイトの選択を推奨します:
また、以下のリストから Web サイトを選択することもできます。
最適なサイトパフォーマンスの取得方法
中国のサイト (中国語または英語) を選択することで、最適なサイトパフォーマンスが得られます。その他の国の MathWorks のサイトは、お客様の地域からのアクセスが最適化されていません。
南北アメリカ
- América Latina (Español)
- Canada (English)
- United States (English)
ヨーロッパ
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom (English)
