C/C++プログラミング仕様


1、注意:strncpy、strncatなどのnバージョンの文字列操作関数は、ソース文字列の長さがn識別の長さを超えた場合、'0'終端子を含む超長文字列を遮断し、'0'終端子が失われる.この場合、ターゲット文字列に手動で'0'の終了文字を設定する必要があります.
    char dst[11];     // 【  】           0: dst[11] = {0};
    char src[] = "0123456789";
    char *tmp = NULL;
    memset(dst, '@', sizeof(dst));
    memcpy(dst, src, strlen(src));
    dst[sizeof(dst) - 1] = ’\0’;    //【  】dst ’\0’  

2、文字列/メモリ操作関数のソースポインタとターゲットポインタがメモリ重複領域を指すことを避ける
memcpy、strcpy、strncpy、sscanf()、sprintf()、snprintf()、wcstombs()のような関数を使用すると、重複オブジェクトをコピーすると未定義の動作があり、データの整合性が損なわれる可能性があります.
memcpyとmemmoveの目的は,いずれもNバイトのソースメモリアドレスの内容をターゲットメモリアドレスにコピーすることである.しかし、ソースメモリとターゲットメモリが重複している場合、memcpyにエラーが発生し、memmoveはコピーを正しく実施できますが、これによりわずかなオーバーヘッドが増加します.memmoveの処理措置:ソースメモリの先頭アドレスがターゲットメモリの先頭アドレスに等しい場合、何のコピーも行わないソースメモリの先頭アドレスがターゲットメモリの先頭アドレスより大きい場合、順方向コピーを実行するソースメモリの先頭アドレスがターゲットメモリの先頭アドレスより小さい場合、逆方向コピーを実行する
3、フォーマット関数を使用する場合、精度説明子の使用を推奨する
#define BUF_SIZE 128
void Compliant()
{
    char buffer[BUF_SIZE + 1];
    sprintf(buffer, "Usage: %.100s argument
"
, argv[0]); /*【 】 */ /* ...do something... */ } // argv[0] 100 。

4、符号なし整数演算で反転しないこと
符号なし数u 1 u 2は、u 1+u 2を計算する際に、u 1+u 2がUINT_より大きいか否かを判断する必要があるMAX
    if((UINT_MAX - ui1) < ui2) //【  】                
    {
        return ERROR;
    }
    else
    {
        *ret = ui1+ ui2;
    }

5、符号整数演算時にオーバーフローしないこと
    INT32 si1, INT32 si2;
    INT64 tmp = (INT64)si1 *(INT64)si2; /*【  】                 */
    //++  32        64 ,                       
    if((INT_MAX < tmp) || (INT_MIN > tmp))
    {
        return ERROR;
    }

6、整数変換時にカットオフエラー、符号エラーが発生しないことを確保する
【遮断エラー】大きい整数を小さい整数に変換し、その数の元の値が小さいタイプの表示範囲を超えると、遮断エラーが発生し、元の値の低位が保持され、高位が破棄されます.【符号エラー】符号付き整数から符号なし整数に変換すると符号エラーが発生し、符号エラーはデータを失うことはないが、データは元の意味を失う.符号付き整数から符号なし整数に変換すると、最上位(high-order bit)は、シンボルビットとしての機能を失う.シンボル付き整数の値が負でない場合、変換後の値は変わらない.シンボル付き整数の値が負の場合、変換後の結果は通常非常に大きな正数である.
    //++[          ]
    int length;       //++        
    char buf[BUF_SIZE];
    length = atoi(argv[1]); //【  】atoi        
    if (length < BUF_SIZE)  // len   ,      
    {
        memcpy(buf, argv[2], length); /*     len    size_t        ,              。memcpy()     buf     */
        printf("Data copied
"
); }

7、整数表現をより大きなタイプに比較または付与する前に、このようなより大きなタイプで評価しなければならない.
UINT32 blockNum;
UINT64 alloc = (UINT64)blockNum * 16; /*【  】                  */
//++     32        64     64    。

8、符号付き整数のビットオペレータ演算を避ける
説明:ビットオペレータ(~、>>>、<<、&、^、|)は、符号なし整数のビット操作の結果がコンパイラによって決定されるため、予想外の動作やコンパイラ定義の動作が発生する可能性があるため、符号なし整数の操作数にのみ使用する必要があります.
9、メモリ管理
メモリを申請した後に初期化(memset)してメモリポインタの移動を禁止した後(malloc割り当て後の開始値ではない)、このポインタでメモリを解放すると未知のエラーが発生します.mallocのメモリがブロックされると、その前のバイトに割り当てられたメモリサイズが格納され、free時にそのバイトが表すサイズに基づいてfreeメモリが除去されます.
10、OSコマンド解析器を呼び出してコマンドを実行したりプログラムを実行したりすることを禁止し、コマンド注入を防止する
システム()とpopen()の使用を禁止します.代替案としては、POSIXのexecシリーズ関数やWin 32 API CreateProcess()など、コマンドインタプリタに関係のないプロセス作成関数がある.エラーの例:
system(sprintf("any_exe %s", input)); //【  】       ,    system

この行のコードはanyという名前を実行する必要があります.exeのプログラム、プログラムパラメータはユーザーの入力inputから来ます.この場合、悪意のあるユーザはパラメータ:happyを入力する.useradd attacker最終shell文字列"any_exe happy; useradd attackerは、2つの独立したコマンドが連続的に実行されると解釈されます.any_exe happy useradd attackerこのように攻撃者はコマンド「useradd attacker」を注入することによって新しいユーザーを作成します.これは明らかにプログラムが望んでいない.変更:
if (execve("/usr/bin/any_exe", args, envs) == -1) /*【  】  execve  system */

11、std::ostrstream使用禁止、std::ostringstream使用推奨
説明:std::ostrstreamの使用には、(1)str()がメンバー関数freeze()を呼び出し、文字シーケンスがフリーズします.バッファが大きくなく、新しいバッファを割り当てる必要がある場合は、複雑なことを避けることができます.(2)str()は文字列終端記号(’0’)を付加しません.(3)data()はすべての文字列を返します.'0'の末尾文字が付いていません(現在、コンパイラによってはc_strメソッドが自動的に呼び出されているものもあります).上記の注意を払わないと、メモリアクセスの限界、バッファオーバーフローなどの問題が発生する可能性がありますので、ostrstreamは使用しないことをお勧めします.[C++03]標準はstd::strstrstreamをdeprecatedと表記し、代替案はstd::stringstreamである.ostringstreamには上記の問題はありません.エラー例:次のコードはstd::ostrstreamを使用しており、メモリアクセスの限界などの問題が発生する可能性があります.void NoCompliant(){std::ostrstream mystr;//【エラー】std::ostrstream mystr<<12、C++では、Cの文字列操作関数の代わりにC++標準ライブラリを使用する必要があります.
C標準のシリーズ文字列処理関数strcpy/strcat/sprintf/scanf/getsは、ターゲットバッファのサイズをチェックせず、バッファオーバーフローのセキュリティホールを導入しやすい.C++標準ライブラリは文字列クラス抽象の共通実装std::stringを提供し、文字列の一般的な操作をサポートする.
13、intタイプを使用して文字入出力関数の戻り値を受信し、charタイプを使用しない
説明:文字入出力関数fgetc()、getc()、getchar()は、いずれも1つのストリームから1つの文字を読み出し、int値として返します.このストリームがファイルの最後に到達したり、読み取りエラーが発生したりした場合、関数はEOFを返します.fputc()、putc()、putchar()、ungetc()も1文字またはEOFを返します.
これらのI/O関数の戻り値をEOFと比較する必要がある場合は、戻り値をcharタイプに変換しないでください.charは符号付き8ビットの値であるため,intは32ビットの値である.getchar()が返す文字のASCII値が0 xFFの場合、charタイプに変換するとEOFと解釈されます.0 xFFという値が符号で拡張されると0 xFFFFFFFFとなり、ちょうどEOFの値に等しい.注意:sizeof(int)==sizeof(char)のプラットフォームでは、intで返される値を受信してもEOFと区別できない場合があります.この場合、feof()とferror()でファイルの末尾とファイルエラーを検出します.
14、ファイルパスを検証する前に、標準化しなければならない
説明:ファイルパスが非信頼ドメインから来た場合は、ファイルパスを正規化してから検証する必要があります.パスは、ファイルのシンボルリンク、ハードリンク、ショートカットパス、別名など、相対パスと絶対パスなど、検証時に多くの干渉要因があります.したがって、経路を検証する際には、経路表現が一意化され、曖昧さがないように経路を標準化する必要がある.標準化されていない場合、攻撃者はチャンスがあります:推奨方法:Linuxの下でファイルを標準化し、ハッカーがシステムの重要なファイルへのリンクファイルを構築することを防止することができます.realpath()関数は絶対パスを返し、すべてのシンボルリンクを削除します.
void  Compliant(char *lpInputPath)
{
    char realpath[MAX_PATH];
    if ( realpath(inputPath, realpath) == NULL)
        /* handle error */;
    /*... do something ...*/
}

Windowsでは、PathCanonicalize関数を使用してファイルパスを標準化できます.
void  Compliant(char *lpInputPath)
{
    char realpath[MAX_PATH];
    char *lpRealPath = realpath;
    if ( PathCanonicalize(lpRealPath,lpInputPath) == NULL)
        /* handle error */;
    /*... do something ...*/
}

15、ファイルにアクセスする時、できるだけファイル名の代わりにファイル記述子を入力として使用し、競争条件の問題を避ける
説明:この推奨適用シーンは次のとおりです.ファイルのメタ情報を操作する場合(所有者を変更したり、ファイルを統計したり、権限ビットを変更したり)まずファイルを開き、開いたファイルを操作します.可能な限り、ファイル名を取得する操作ではなく、ファイル記述子を取得する操作を使用します.これにより、プログラムの実行時にファイルが置換されないようにします.(可能な競合条件).たとえば、access()とopen()の両方がファイルハンドルではなく文字列パラメータを使用して関連操作を行う場合、攻撃者はaccess()とopen()の間の隙間で元のファイルを置き換えることができます.エラーの例:次のコードはaccess()関数を使用して競合条件の問題を引き起こす可能性があります.
void  Noncompliant(char * file)
{
    if(!access(file, W_OK))     //【   】      access(),       
    {
        f = fopen(file, "w+");
        /*...*/
        /* close f after operate(f)*/
    }
    else 
    {
        fprintf(stderr, "Unable to open file %s.
"
, file); } }

16、容器を正しく処理するerase()方法と反復子の関係
説明:コンテナのerase(iter)メソッドを呼び出すと、反復子が指すオブジェクトが解析され、反復子は失効し、反復子に対してインクリメンタル減算または参照操作を実行するとプログラムがクラッシュします.
//++     :
    m_mapID2NE.erase(iter); 
    iter++;  //【  】erase ,iter          
//++     :
    m_mapID2NE.erase(iter++); //【  】          erase   

削除された要素反復子が指す次の要素位置:iter = erase(iter)を返すため、earseメソッドの戻り値を使用して反復子を保存することもできます.注意この用法はlistとvectorのerase()に用いることができるがmapには適用されない.std::map::erase()の戻り値は、STL実装バージョンによって異なり、戻り値がある場合もあれば、戻り値がない場合もあるので、mapには推奨方法しか使用できません.