メインコンテンツ

ISO/IEC TS 17961 [taintformatio]

Using a tainted value to write to an object using a formatted input or output function

説明

ルール定義

書式設定付きの入力関数または出力関数を使用してオブジェクトに書き込む際に汚染された値を使用。1

Polyspace 実装

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

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

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

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

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

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

  • 汚染された文字列形式指定子

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

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

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

チェッカーの拡張

既定の Bug Finder 解析では、現在の解析境界の外部からの特定の入力に関する "汚染された NULL 文字列または非 NULL 終端文字列" または "汚染された文字列形式指定子" 問題にフラグを設定しない場合があります。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

すべて展開する

問題

不正確な文字列形式指定子によるバッファー オーバーフローは、sscanf のような関数の形式指定子の引数が、メモリ バッファー引数でのオーバーフローまたはアンダーフローにつながる場合に発生します。

リスク

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

修正方法

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

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

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

この例では、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);
}

この例では、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);
}
問題

この問題は、標準ライブラリの関数で無効な引数が使用された場合に発生します。この欠陥では、浮動小数点、整数、メモリまたは文字列の標準ライブラリ ルーチンでカバーされない他の関数に関係するエラーが検出されます。

リスク

標準ライブラリ関数で無効な引数を使用すると未定義の動作が発生します。

修正方法

修正方法は欠陥の根本原因によって異なります。たとえば、ポインターが NULL で初期化されて、特定の実行パスでその初期値が上書きされない場合、関数 printf の引数が NULL になる可能性があります。

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

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

例 – 文字列なしでの printf の呼び出し
#include <stdio.h>
#include <stdlib.h>

void print_null(void) {

  printf(NULL); 
}

関数 printf は文字列の入力引数または書式指定子のみを取ります。この関数では入力値が NULL であるため、有効な文字列ではありません。

修正 — 互換性のある入力引数を使用

1 つの修正方法として、入力引数を標準ライブラリ ルーチンの要件に適合するよう変更することができます。この例では、入力引数を文字に変更しました。

#include <stdio.h>

void print_null(void) {
    char zero_val = '0';
    printf((const char*)zero_val); 
}
問題

標準ライブラリ文字列ルーチンの無効な使用は、文字列ライブラリ関数が無効な引数で呼び出された場合に発生します。

リスク

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

修正方法

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

char * strcpy(char * destination, const char* source);
これが、使用可能なバッファーに比べて大きすぎるバイト数をコピー先引数にコピーしようとする場合、strcpy を呼び出す前にコピー元引数を制約します。場合によっては、代替となる関数を使用してエラーを回避できます。たとえば、strcpy の代わりに strncpy を使用するとコピーされるバイト数を制御できます。Polyspace デスクトップ ユーザー インターフェイスでの Bug Finder の結果の解釈も参照してください。

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

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

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

  res=strcpy(gbuffer,text); 
  /* 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);
 }
問題

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

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

リスク

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

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

修正方法

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

  • 文字列が NULL でない。

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

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

例 – 文字列を入力引数から取得
#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));
	print_str(str);
}



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

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

修正 1 — データを検証

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)) { // Defect only raised here 
		// - 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);
}
修正 2 — データを検証

別の修正方法として、特定の文字列を含む関数 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;
}
問題

この問題は、printf 形式の関数で、セキュリティで保護されていないソースから構築された書式指定子が使用された場合に発生します。

リスク

外部で制御される要素を使用して文字列を形式化すると、バッファー オーバーフローやデータ表現の問題の原因となることがあります。攻撃者はこうした文字列形式の設定要素を利用して、%x でスタックの内容を表示し、あるいは %n でスタックへの書き込みを行うことができます。

修正方法

静的文字列を渡して文字列関数を形式化します。この修正により、外部アクターは文字列を制御できなくなります。

別の修正方法として、必要な数の引数のみを許可します。可能な場合は、脆弱性のある %n 演算子をサポートしない関数を、文字列形式で使用します。

例 – ユーザー入力から要素を取得
#include <stdio.h>
#include <unistd.h>
#define MAX 40
void taintedstringformat(void) {
	char userstr[MAX];
	read(0,userstr,MAX);
	printf(userstr);   
}

この例では、入力引数 userstr を出力しています。この文字列は不明です。そこに % などの要素が含まれている場合、printf では userstr が文字列でなく文字列形式と解釈され、プログラムがクラッシュする原因となります。

修正 — 文字列として出力

1 つの修正方法として、userstr を明示的に文字列として出力し、あいまいさをなくします。

#include <stdio.h>
#include <unistd.h>
#define MAX 40
void taintedstringformat(void) {
	char userstr[MAX];
	read(0,userstr,MAX);
	printf("%.20s", userstr);;   
}
問題

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

危険な関数リスク レベルより安全な関数
gets本質的に危険 — コンソールからは入力の長さを制御できない。fgets
cin本質的に危険 — コンソールからは入力の長さを制御できない。使用を避けるか、cin の呼び出しの前に cin.width を追加する。
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)
    {
        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;
}
問題

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

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

リスク

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

修正方法

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

または、関数 strcpy() の代わりに関数 strncpy() を使用します。関数 strncpy() は、既知の数の文字をソース バッファーから格納先バッファーにコピーします。関数 strncpy() は既知の数の文字をコピーするため、strcpy() よりも安全な代替手段です。

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

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

#include <string.h>
  
int main(int argc, char *argv[]) {
  const char *const source = (argc && argv[0]) ? argv[0] : "";
  char destination[128];
  strcpy(destination,source);//Noncompliant
  
  return 0;
}
修正 — strncpystrcpy の代わりに使用

この問題を修正するには、strncpy() を使用して、既知の数の文字を source バッファーから destination バッファーにコピーします。

#include <string.h>
#define MAX_ARGS  128  
int main(int argc, char *argv[]) {
  const char *const source = (argc && argv[0]) ? argv[0] : "";
  char destination[MAX_ARGS];
  int num = (strlen(source)+1>MAX_ARGS)?MAX_ARGS:strlen(source)+1;
  strncpy(destination,source,num);//Compliant
  
  return 0;
}

修正 — 格納先バッファーに十分なメモリを割り当てる

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

#include <string.h>

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(destination, source);//Compliant
	}else{
		/*Handle Error*/
	}
	//...
	free(destination);
	return 0;
}

チェック情報

決定可能性:決定不可能

バージョン履歴

R2019a で導入


1 Extracts from the standard "ISO/IEC TS 17961 Technical Specification - 2013-11-15" are reproduced with the agreement of AFNOR. Only the original and complete text of the standard, as published by AFNOR Editions - accessible via the website www.boutique.afnor.org - has normative value.