メインコンテンツ

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

非トリビアルなクラス オブジェクトのバイト演算

値の表現が不適切に初期化または比較される可能性がある

説明

この欠陥は、C 標準ライブラリ関数を使用して、非トリビアルまたは非標準のレイアウト クラス型オブジェクトに対してバイト単位演算を実行した場合に発生します。トリビアルおよび標準のレイアウト クラスに関する定義については、C++ Standard (ISO/IEC 14882:2017)、[class]、段落 6 および 7 をそれぞれ参照してください。

チェッカーは以下の場合に欠陥を報告します。

  • 次の関数を使用して非トリビアルなクラス型オブジェクトを初期化またはコピーする。

    • std::memset

    • std::memcpy

    • std::strcpy

    • std::memmove

    クラス型がトリビアルであるかどうかを確認するには、type-traits ライブラリ関数 std::is_trivial を次のように使用します。

    #include <iostream>
    #include <type_traits>
    
    class trivialClass {};
    
    void checkTrivial(){
        static_assert(std::is_trivial<trivialClass>::value,
                      "Class is not trivial");
    }
    オブジェクト型がトリビアル コピー可能 (std::is_trivially_copyable<>:value) であることを確認するだけでは十分ではありません。プログラムの後半でオブジェクトを使用する場合、トリビアル コピー可能オブジェクトでは、クラスの不変性の維持が保証されません。

  • 次の関数を使用して非標準のレイアウト クラス型オブジェクトを比較する。

    • std::memcmp

    • std::strcmp

不完全なクラスは非トリビアルである可能性があることに注意してください。

チェッカーは、バイト単位演算がエイリアスを介して実行された場合には欠陥を報告しません。たとえば、このコードによるバイト比較演算およびバイト コピー演算では欠陥が報告されません。このバイト演算では、dptr および sptr を使用しています。これらは非トリビアルまた非標準のレイアウト クラス オブジェクト d および s のエイリアスです。

void func(NonTrivialNonStdLayout *d, const NonTrivialNonStdLayout *s)
{
   void* dptr = (void*)d; 
   const void* sptr = (void*)s;
   // ...
   // ...
   // ...
   if (!std::memcmp(dptr, sptr, sizeof(NonTrivialNonStdLayout))) {  
     (void)std::memcpy(dptr, sptr, sizeof(NonTrivialNonStdLayout)); 
      // ...
   }
}

リスク

非トリビアルまたは非標準のレイアウト クラス型オブジェクトに対して、C 標準ライブラリ関数を使用してバイト比較演算を実行すると、実装の詳細による予期しない値につながる可能性があります。オブジェクト表現は、実装の詳細 (プライベートおよびパブリック メンバーの順序など)、またはそのオブジェクトを表すバーチャル関数ポインター テーブルの使用に依存します。

非トリビアルまたは非標準のレイアウト クラス型オブジェクトに対して、C 標準ライブラリ関数を使用してバイト設定演算を実行すると、実装の詳細が変更される可能性があります。この演算により、プログラムの異常動作やコード実行の脆弱性につながる可能性があります。たとえば、メンバー関数のアドレスが上書きされた場合、その関数を呼び出すと予期しない関数が呼び出されます。

修正方法

非トリビアルまたは非標準のレイアウト クラス型オブジェクトにバイト演算を実行するには、C 標準ライブラリ関数ではなく、次の特殊な C++ メンバー関数を使用します。

C 標準ライブラリ関数C++ メンバー関数

std::memset

クラスのコンストラクター

std::memcpy

std::strcpy

std::memmove

クラスのコピー コンストラクター

クラスの移動コンストラクター

コピー代入演算子

移動代入演算子

std::memcmp

std::strcmp

operator<()

operator>()

operator==()

operator!=()

すべて展開する

#include <cstring>
#include <iostream>
#include <utility>

class nonTrivialClass
{
    int scalingFactor;
    int otherData;
public:
    nonTrivialClass() : scalingFactor(1) {}
    void set_other_data(int i);
    int f(int i)
    {
        return i / scalingFactor;
    }
    // ...
};

void func()
{
    nonTrivialClass c;
    // ... Code that mutates c ...
    std::memset(&c, 0, sizeof(nonTrivialClass));
    std::cout << c.f(100) << std::endl;
}

この例では、func()std::memset を使用して、既定のコンストラクターで初期化された非トリビアルなクラス オブジェクト c を再初期化しています。このバイト演算は、値 c の表現を適切に初期化しない可能性があります。

修正 — std::swap を使用する関数テンプレートを定義

1 つの修正方法として、std::swap を使用して交換演算を実行する関数テンプレート clear() を定義します。clear() を呼び出すと、オブジェクト c の内容と既定の初期化が施されたオブジェクト empty の内容が交換され、c は適切に再初期化されます。

 #include <cstring>
#include <iostream>
#include <utility>

class nonTrivialClass
{
    int scalingFactor;
    int otherData;
public:
    nonTrivialClass() : scalingFactor(1) {}
    void set_other_data(int i);
    int f(int i)
    {
        return i / scalingFactor;
    }
    // ...
};

template <typename T>
T& clear(T& o)
{
    using std::swap;
    T empty;
    swap(o, empty);
    return o;
}

void func()
{
    nonTrivialClass c;
    // ... Code that mutates c ...

    clear(c);
    std::cout << c.f(100) << std::endl;
}

結果情報

グループ: オブジェクト指向
言語: C++
既定値: オフ
コマンド ライン構文: MEMOP_ON_NONTRIVIAL_OBJ
影響度: Medium

バージョン履歴

R2019b で導入