CWE Rule 825
説明
ルールの説明
The program dereferences a pointer that contains a location for memory that was previously valid, but is no longer valid.
Polyspace 実装
ルール チェッカーは以下の問題をチェックします。
有効期間が一時的なオブジェクトにアクセスしています
以前に割り当て解除したポインターの解放
前の操作によって無効になった環境ポインター
スタック変数へのポインターまたは参照が範囲外
putenv ファミリ関数の引数としての自動変数の使用
前に解放したポインターの使用
例
この問題は、関数呼び出しから返された、有効期間が一時的なオブジェクトに対して、読み取りまたは書き込みを試みると発生します。関数から返された、配列が含まれている構造体または共用体では、それらの配列メンバーは一時オブジェクトです。一時オブジェクトの有効期間が終了するのは、以下のとおりです。
C11 規格で定義されているように、その呼び出しを含む完全な式または完全な宣言子が終了したとき。
C90 および C99 規格で定義されているように、次のシーケンス ポイントの後。シーケンス ポイントとは、プログラムの実行において、すべての前の評価が完了し、以降の評価がまだ開始されていない時点のことです。
C++ コードでは、有効期間が一時的なオブジェクトに書き込みを行う場合のみ、[有効期間が一時的なオブジェクトにアクセスしています] によって欠陥が報告されます。
有効期間が一時的なオブジェクトがアドレスで返された場合、欠陥は報告されません。
有効期間が一時的なオブジェクトの変更は未定義の動作であり、プログラムの異常終了と移植性の問題の原因になる可能性があります。
関数呼び出しから返されたオブジェクトをローカル変数に代入します。有効期間が一時的なオブジェクトの内容がこの変数にコピーされます。安全にこれを変更できるようになります。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define SIZE6 6
struct S_Array
{
int t;
int a[SIZE6];
};
struct S_Array func_temp(void);
/* func_temp() returns a struct value containing
* an array with a temporary lifetime.
*/
int func(void) {
/*Writing to temporary lifetime object is
undefined behavior
*/
return ++(func_temp().a[0]); //Noncompliant
}
void main(void) {
(void)func();
}
この例では、func_temp()
は配列メンバー a
をもつ構造体を値で返します。このメンバーの有効期間は一時的です。これをインクリメントすることは未定義の動作です。
1 つの修正方法として、func_temp()
の呼び出しの戻り値をローカル変数に代入します。一時オブジェクト a
の内容がこの変数にコピーされ、これを安全にインクリメントできます。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define SIZE6 6
struct S_Array
{
int t;
int a[SIZE6];
};
struct S_Array func_temp(void);
int func(void) {
/* Assign object returned by function call to
*local variable
*/
struct S_Array s = func_temp();
/* Local variable can safely be
*incremented
*/
++(s.a[0]);
return s.a[0];
}
void main(void) {
(void)func();
}
この問題は、メモリのブロックが、関数 free
を使用して中間割り当てなしで、連続して解放された場合に発生します。
malloc
、calloc
、または realloc
を使用してポインターに動的メモリが割り当てられている場合、ポインターはヒープ上のメモリ位置を指します。このポインターに対して関数 free
を使用すると、メモリの関連ブロックが再割り当て用に解放されます。メモリのこのブロックを解放しようとすると、セグメンテーション違反が発生する可能性があります。
修正方法は欠陥の根本原因によって異なります。最初の割り当て解除と 2 番目の割り当て解除の間でメモリ ブロックをポインターに割り当てることを意図しているかどうかを確認します。そうでない場合は、2 番目の free
ステータスを削除します。
メモリ ブロックを解放した後、対応するポインターに NULL を割り当てることをお勧めします。ポインターを解放する前に、NULL 値かどうかをチェックしてエラーを処理します。この方法により、既に解放されているブロックを解放するのを避けることができます。
#include <stdlib.h>
void allocate_and_free(void)
{
int* pi = (int*)malloc(sizeof(int));
if (pi == NULL) return;
*pi = 2;
free(pi);
free (pi); //Noncompliant
/* Defect: pi has already been freed */
}
最初の free
ステートメントにより、pi
が参照するメモリのブロックが解放されます。pi
に対する 2 番目の free
ステートメントにより、既に解放されたメモリが解放されます。
1 つの修正方法として、2 番目の free
ステートメントを削除することができます。
#include <stdlib.h>
void allocate_and_free(void)
{
int* pi = (int*)malloc(sizeof(int));
if (pi == NULL) return;
*pi = 2;
free(pi);
/* Fix: remove second deallocation */
}
#include <stdlib.h>
void reshape(char *buf, size_t size) {
char *reshaped_buf = (char *)realloc(buf, size);
if (reshaped_buf == NULL) {
free(buf); //Noncompliant
}
}
この例では、関数 reshape()
の引数 size
がゼロになり、その結果、realloc()
でサイズがゼロの再割り当てが行われる可能性があります。GNU® ライブラリなどの一部の実装では、サイズがゼロの再割り当てによってメモリが解放されると、二重解放の欠陥につながります。
1 つの修正方法として、使用する前に realloc()
のサイズ引数の値がゼロかどうかをチェックします。サイズ引数がゼロの場合は、メモリを再割り当てするのではなく、単に解放します。
#include <stdlib.h>
void reshape(char *buf, size_t size) {
if (size != 0) {
char *reshaped_buf = (char *)realloc(buf, size);
if (reshaped_buf == NULL) {
free(buf);
}
}
else {
free(buf);
}
}
この問題は、ホスト環境での操作によって環境が変更された後に、"main()" の第 3 引数を使用して環境にアクセスした場合に発生します。ホストされている環境では、多くの C 実装で非標準の構文がサポートされます。
main (int argc, char *argv[], char *envp[])
setenv
または putenv
ファミリ関数の呼び出しにより、*envp
の参照先の環境に変更が加えられます。setenv
または putenv
ファミリ関数の呼び出しによって環境が変更されると、環境のメモリが再割り当てされる可能性があります。そのホストされている環境のポインターが更新されず、正しくない場所を指す場合があります。このポインターを呼び出すと、予期しない結果が返されたり、プログラムの異常終了が発生したりすることがあります。
ホストされている環境のポインターを使用しないようにします。代わりに、グローバル外部変数を使用します。Linux® では environ
、Windows® では _environ
や _wenviron
、またはこれらと同等のグローバル外部変数を使用します。環境を変更すると、これらの変数が更新されます。
#include <stdio.h>
#include <stdlib.h>
extern int check_arguments(int argc, char **argv, char **envp);
extern void use_envp(char **envp);
/* envp is from main function */
int func(char **envp)
{
/* Call to setenv may cause environment
*memory to be reallocated
*/
if (setenv(("MY_NEW_VAR"),("new_value"),1) != 0)
{
/* Handle error */
return -1;
}
/* envp not updated after call to setenv, and may
*point to incorrect location.
**/
if (envp != ((void *)0)) { //Noncompliant
use_envp(envp);
/* No defect on second access to
*envp because defect already raised */
}
return 0;
}
void main(int argc, char **argv, char **envp)
{
if (check_arguments(argc, argv, envp))
{
(void)func(envp);
}
}
この例では、環境のメモリが再割り当てされる可能性のある setenv
の呼び出しの後に、func()
内で envp
にアクセスしています。envp
は、setenv
によって環境が変更された後には更新されないため、正しくない場所を指す可能性があります。前のコード行で既に欠陥が報告されているため、use_envp()
が呼び出されたときには欠陥が報告されません。
environ
を使用1 つの修正方法として、setenv
の呼び出し後に必ず更新される変数を使用して環境にアクセスします。たとえば、次のコードでは、ポインター envp
は main()
からも使用可能ですが、環境へのアクセスは func()
内でグローバル外部変数 environ
を通じて行います。
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
extern int check_arguments(int argc, char **argv, char **envp);
extern void use_envp(char **envp);
int func(void)
{
if (setenv(("MY_NEW_VAR"), ("new_value"),1) != 0) {
/* Handle error */
return -1;
}
/* Use global external variable environ
*which is always updated after a call to setenv */
if (environ != NULL) {
use_envp(environ);
}
return 0;
}
void main(int argc, char **argv, char **envp)
{
if (check_arguments(argc, argv, envp))
{
(void)func();
}
}
この問題は、ローカル変数へのポインターまたは参照が変数のスコープを逸脱している場合に発生します。次に例を示します。
関数が、ローカル変数を指すポインターを返す。
関数が代入
globPtr = &locVar
を実行する。globPtr
はグローバル ポインター変数、locVar
はローカル変数です。関数が代入
*paramPtr = &locVar
を実行する。paramPtr
は関数パラメーター (つまりint**
ポインターなど)、locVar
はローカルのint
変数です。C++ メソッドが代入
memPtr = &locVar
を実行する。memPtr
はメソッドが属するクラスのポインター データ メンバー、locVar
はメソッドから見てローカルな変数です。(C++11 以降) 関数が、参照によって関数のローカル変数を取得するラムダ式オブジェクトを返す。
欠陥は、関数 alloca
を使用して割り当てたメモリにも適用されます。この欠陥は静的なローカル変数には適用されません。Polyspace® は、関数定義に含まれるローカル オブジェクトは同じスコープ内にあると仮定します。
ローカル変数にはスタック上のアドレスが割り当てられます。ローカル変数のスコープがいったん終了すると、このアドレスは再利用可能になります。このアドレスを使用して変数のスコープ外にあるローカル変数値にアクセスすると、予期しない動作を引き起こす可能性があります。
ローカル変数を指すポインターが変数のスコープを逸脱していると、Polyspace Bug Finder™ によってその欠陥が強調表示されます。この欠陥は、ポインターに格納されているアドレスが使用されていない場合でも発生します。コードを保守可能なものにするため、ポインターが変数のスコープを逸脱しないようにすることをお勧めします。ポインター内のアドレスが現在使用されていない場合でも、関数の他の使用者がそのアドレスを使用することで動作が未定義となる可能性があります。
ローカル変数へのポインターまたは参照が変数スコープを逸脱しないようにします。
void func2(int *ptr) {
*ptr = 0;
}
int* func1(void) {
int ret = 0; //Noncompliant
return &ret ;
}
void main(void) {
int* ptr = func1() ;
func2(ptr) ;
}
この例では、func1
はローカル変数 ret
を指すポインターを返します。
main
では、ptr
はローカル変数のアドレスを指します。ret
のスコープは func1
に制限されているため、func2
内で ptr
がアクセスされると、そのアクセスは無効になります。
auto createAdder(int amountToAdd) {
int addThis = amountToAdd; //Noncompliant
auto adder = [&] (int initialAmount) {
return (initialAmount + addThis);
};
return adder;
}
void func() {
auto AddByTwo = createAdder(2);
int res = AddByTwo(10);
}
この例では、関数 createAdder
で、参照によってローカル変数 addThis
を取得するラムダ式 adder
を定義しています。addThis
のスコープは関数 createAdder
に制限されます。createAdder
によって返されたオブジェクトが呼び出されると、変数 addThis
に対する参照がスコープ外からアクセスされます。この方法でアクセスが行われると、addThis
の値は未定義となります。
関数がラムダ式オブジェクトを返す場合は、ラムダ式でローカル変数を参照によって取得しないようにします。代わりにコピーによって変数を取得します。
コピーによって取得された変数の有効期間はラムダ オブジェクトと同じです。しかし、参照によって取得された変数の有効期間は、多くの場合、ラムダ オブジェクト自体よりも短くなります。ラムダ オブジェクトを使用すると、スコープ外からアクセスされるこれらの変数は値が未定義となります。
auto createAdder(int amountToAdd) {
int addThis = amountToAdd;
auto adder = [=] (int initialAmount) {
return (initialAmount + addThis);
};
return adder;
}
void func() {
auto AddByTwo = createAdder(2);
int res = AddByTwo(10);
}
この問題は、putenv
ファミリ関数の引数が自動持続期間をもつローカル変数である場合に発生します。
関数 putenv(char *string)
は、引数のコピーを作成するのではなく、指定された引数を指すポインターを環境配列に挿入します。引数が自動変数の場合、putenv()
呼び出しを含む関数から値が返された後、メモリが上書きされる可能性があります。その後、別の関数が getenv()
を呼び出した場合、適切にデリファレンスできないスコープ外の変数のアドレスが返されます。このスコープ外の変数は、環境変数が予期しない値を取る、プログラムが応答を停止する、任意のコード実行を可能にする脆弱性を生むなどの原因になる可能性があります。
環境変数の設定/設定解除に setenv()
/unsetenv()
を使用します。または、putenv
ファミリ関数の引数と動的に割り当てられたメモリを使用するか、アプリケーションに再入要件がない場合は引数と静的存続期間を使用します。たとえば、再帰や割り込みのないシングルスレッド実行には再入は必要ありません。実行中の呼び出し (再入) はできません。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE1024 1024
void func(int var)
{
char env[SIZE1024];
int retval = sprintf(env, "TEST=%s", var ? "1" : "0");
if (retval <= 0) {
/* Handle error */
}
/* Environment variable TEST is set using putenv().
The argument passed to putenv is an automatic variable. */
retval = putenv(env); //Noncompliant
if (retval != 0) {
/* Handle error */
}
}
この例では、sprintf()
は文字列 TEST=var
を env
に格納します。その後、環境変数 TEST
の値が putenv()
を使用して var
に設定されます。env
は自動変数であるので、TEST
の値は func()
が値を返すときに変化する可能性があります。
putenv()
の引数に static
変数を使用env
を静的存続期間の変数として宣言します。env
のメモリ位置は、func()
が値を返した後でも、プログラムの存続期間中は上書きされません。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE1024 1024
void func(int var)
{
/* static duration variable */
static char env[SIZE1024];
int retval = sprintf(env,"TEST=%s", var ? "1" : "0");
if (retval <= 0) {
/* Handle error */
}
/* Environment variable TEST is set using putenv() */
retval=putenv(env);
if (retval != 0) {
/* Handle error */
}
}
setenv()
を使用して環境変数値を設定TEST
の値を var
に設定するために、setenv()
を使用します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE1024 1024
void func(int var)
{
/* Environment variable TEST is set using setenv() */
int retval = setenv("TEST", var ? "1" : "0", 1);
if (retval != 0) {
/* Handle error */
}
}
この問題は、メモリのブロックを関数 free
を使用して解放した後で、そのブロックにアクセスした場合に発生します。
malloc
、calloc
、または realloc
を使用してポインターに動的メモリが割り当てられている場合、ポインターはヒープ上のメモリ位置を指します。このポインターに対して関数 free
を使用すると、メモリの関連ブロックが再割り当て用に解放されます。メモリのこのブロックにアクセスしようとすると、予測できない動作やセグメンテーション違反が発生する可能性があります。
修正方法は欠陥の根本原因によって異なります。メモリを後で解放することを意図しているのか、またはアクセスする前に別のメモリ ブロックをポインターに割り当てることを意図しているのかを確認します。
メモリ ブロックを解放した後、対応するポインターに NULL を割り当てることをお勧めします。ポインターをデリファレンスする前に、NULL 値かどうかをチェックしてエラーを処理します。この方法により、解放されているブロックにアクセスするのを避けることができます。
#include <stdlib.h>
#include <stdio.h>
int increment_content_of_address(int base_val, int shift)
{
int j;
int* pi = (int*)malloc(sizeof(int));
if (pi == NULL) return 0;
*pi = base_val;
free(pi);
j = *pi + shift; //Noncompliant
/* Defect: Reading a freed pointer */
return j;
}
free
ステートメントにより、pi
が参照するメモリのブロックが解放されます。そのため、free
ステートメント後の pi
のデリファレンスは有効ではありません。
1 つの修正方法として、最後のアクセス インスタンスの後にのみポインター pi
を解放することができます。
#include <stdlib.h>
int increment_content_of_address(int base_val, int shift)
{
int j;
int* pi = (int*)malloc(sizeof(int));
if (pi == NULL) return 0;
*pi = base_val;
j = *pi + shift;
*pi = 0;
/* Fix: The pointer is freed after its last use */
free(pi);
return j;
}
チェック情報
カテゴリ: Pointer Issues |
バージョン履歴
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)