メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

CERT C++: STR50-CPP

Guarantee that storage for strings has sufficient space for character data and the null terminator

説明

ルール定義

文字列のストレージに、文字データと NULL 終端のための十分な領域があることを保証します。1

Polyspace 実装

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

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

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

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

  • 格納先バッファー サイズが不十分

  • null で終了していない入力文字列

すべて展開する

問題

危険な標準関数を使用していますチェックでは、特定の環境において本質的に危険か潜在的に危険な関数の使用が強調表示されます。次の表に、潜在的に危険な関数、各関数を使用する場合のリスクおよび代用する関数をリストします。

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

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

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

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
リスク

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

修正方法

修正方法は欠陥の根本原因によって異なります。多くの場合、結果の詳細には欠陥につながる一連のイベントが表示されます。そのシーケンス内のどのイベントについても修正を実装できます。結果の詳細にイベント履歴が表示されない場合は、ソース コード内で右クリック オプションを使用して逆のトレースを行い、これまでの関連するイベントを確認できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。

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

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

例 - 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;
}
問題

不正確な文字列形式指定子によるバッファー オーバーフローは、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);
}
問題

文字列操作で格納先バッファーがオーバーフローしていますは、特定の文字列操作関数が、その格納先バッファー引数にバッファー サイズより大きいオフセットで書き込む場合に発生します。

たとえば、関数 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);
}
問題

"格納先バッファー サイズが不十分" は、strcpy 演算内の格納先バッファーにソース バッファーと null 終端が収まらない場合に発生します。この問題は、ソース バッファーのサイズが不明な場合に報告されます。次のコードについて考えます。

int main (int argc, char *argv[])
{
  const char *const input = ((argc && argv[0]) ? argv[0] : "");
  char str[100];
  strcpy(input, str); // Noncompliant
}
このケースでは、ソース バッファー input のサイズが不明です。格納先バッファー str のサイズが、値 (strlen(input)+1) より小さい可能性があります。Polyspace® は、strcpy 演算に対する違反を報告します。

リスク

サイズが不十分な格納先バッファーを使用すると、攻撃者がバッファー オーバーフローを引き起こすことができるようになる可能性があります。前のコード例では、argv[0] に 100 個以上の文字が含まれている場合に、strcpy 演算でバッファー オーバーフローが発生します。

修正方法

関数 strcpy() を呼び出す前に、十分なメモリを動的に割り当てます。たとえば、関数 strlen() を使用してソース バッファーのサイズを決定してから、格納先バッファーを、そのサイズが値 strlen(source) + 1 を上回るように割り当てます。

例 — 格納先バッファーが小さすぎる

この例では、source バッファーのサイズは不明ですが、destination バッファーのサイズは 128 で固定されます。この destination バッファーのサイズは、source バッファーからの文字を収め、そのバッファーを null で終了するには不十分な可能性があります。Polyspace は、ルールの違反を報告します。

#include <cstring>

int main(int argc, char *argv[]) {
	const char *const source = (argc && argv[0]) ? argv[0] : "";
	char destination[128];
	strcpy(const_cast<char*>(source), destination);//Noncompliant
	return 0;
}
修正 — 格納先バッファーに十分なメモリを割り当てる

この違反は、destination バッファーに十分なメモリを割り当てることによって解決されます。たとえば、関数 strlen() を使用して source バッファーのサイズを計算し、source バッファーからのすべての文字と null 終端 ('\0') を収めるのに十分なメモリを destination バッファーに割り当てます。

#include <cstring>
#include <cstdlib>

int main(int argc, char *argv[]) {
	const char *const source = (argc && argv[0]) ? argv[0] : "";
	char* destination = (char *)malloc(strlen(source)+ 1);
	if(destination!=NULL){
		strcpy(const_cast<char*>(source), destination);//Compliant
	}else{
		/*Handle Error*/
	}
	//...
	free(destination);
	return 0;
}
問題

問題 "null で終了していない入力文字列" は、以下の条件の両方に該当する場合に発生します。

  • 必ずしも文字列を null 文字で終了するわけではない入力関数から文字列を取得している。

  • null で終了する文字列が想定されている場所で、null 文字で終了していない入力文字列を使用している。

次に例を示します。

    char str[10];
    scanf("%10c", str);
    std::string S(str);//Null-terminated string expected
この例での std::string のコンストラクターは null 文字で終了する文字列を想定していますが、str は null で終了していません。Polyspace は、このルールの違反を報告します。

リスク

C++ ライブラリ内の関数は、有効な文字列は null 文字で終了すると仮定し、この仮定を文字列アルゴリズムに適用します。これらの関数とアルゴリズムで終了 null 文字がない文字列を使用すると、未定義の動作につながります。

修正方法

この問題の修正は、コンテキストによって異なります。

  • この問題を修正するには、生の入力文字列を null で終了させます。たとえば、以下のコードでは 11 個の要素で str を宣言しています。最初の 10 個の要素に生の入力を取り込んだ後、最後の要素に値 '\0' を代入します。手動で入力文字列を null 文字で終了させた後は、このルールに違反することなく、この入力文字列を標準テンプレート ライブラリ (STL) の文字列関数で使用できます。

        char str[11];
        scanf("%10c", str);
        str[10] = '\0';
        std::string S(str);//Valid string

  • 一部の C++ 関数は、文字ポインターまたは部分文字列を入力として受け入れます。これらの関数は、null 文字で終了していない生の文字配列を正しく処理できます。たとえば、以下のコードは既定以外のコンストラクターを使用して std::string オブジェクト S を構成します。このコンストラクターでは、終了 null 文字がない str を使用できます。このような使用法は、このルールに違反しません。

        char str[10];
        scanf("%10c", str);
        std::string S(str,sizeof(str));//Valid use of substring

例 — 終了 null 文字のない入力文字列の出力

この例では、関数 read が文字配列を受け入れた後、その文字配列が char 配列 buffer に格納されます。buffer には終了 null 文字がないため、関数 printf()buffer を使用すると、未定義の動作につながります。Polyspace は、このルールの違反を報告します。

#include <iostream>
#include <string>
#include <fstream>
#include <cstdio>
void echo_instream(std::istream& instream) {
    //...
    char buffer[10];
    instream.read(buffer,sizeof(buffer));
	//...
    printf("%s", buffer); //Noncompliant
	//...
}

修正 — 入力文字列を '\0' で終了させる

この問題を修正するには、buffer の末尾に終了 null 文字を手動で追加します。

#include <iostream>
#include <string>
#include <fstream>
#include <cstdio>
void echo_instream(std::istream& instream) {
    //...
    char buffer[10];
    instream.read(buffer,sizeof(buffer)-1);
    buffer[9] = '\0';
	//...
    printf("%s", buffer); //Compliant
	//...
}

チェック情報

グループ: 05.文字と文字列 (STR)

バージョン履歴

R2019a で導入

すべて展開する


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.