web-dev-qa-db-ja.com

標準ライブラリのどの関数を避けなければならない(すべき)ですか?

Stack Overflowで、一部のC関数は「廃止」または「回避する必要がある」ことを読みました。このような機能の例とその理由を教えてください。

これらの機能に代わるものは何ですか?

それらを安全に使用できますか?

90
Andrei Ciobanu

非推奨の関数
安全でない
このような関数の完全な例は、 gets() です。これは、宛先バッファの大きさを伝える方法がないためです。したがって、gets()を使用して入力を読み取るプログラムには、 バッファオーバーフローの脆弱性 があります。同様の理由で、 strcpy() の代わりに strncpy() を使用し、 strcat() の代わりに strncat() を使用する必要があります。

さらにいくつかの例には、 tmpfile() および mktemp() 関数が含まれます。これは、 一時ファイルの上書きに関する潜在的なセキュリティ問題 であり、より安全な mkstemp() に置き換えられます関数。

非再入可能
他の例には、 gethostbyaddr() および gethostbyname() が含まれますが、これらは再入不可であり(したがって、スレッドセーフであるとは限りません)、再入可能オブジェクト getaddrinfo( ) および freeaddrinfo()

ここでパターンに気づいているかもしれません...セキュリティの欠如(おそらく、安全に実装するために十分な情報を署名に含めないことによる)または非再入可能性が非推奨の一般的な原因です。

時代遅れ、非ポータブル
他の一部の機能は機能が重複しており、他のバリアントほど移植性がないため、単に非推奨になります。たとえば、 bzero() は非推奨になり、 memset() が優先されます。

スレッドの安全性と再入
投稿でスレッドの安全性と再入について質問しました。若干の違いがあります。共有された変更可能な状態を使用しない場合、関数は再入可能です。したがって、たとえば、必要なすべての情報が関数に渡され、必要なバッファーも(関数へのすべての呼び出しで共有されるのではなく)関数に渡される場合、再入可能です。これは、独立したパラメーターを使用することにより、異なるスレッドが誤って状態を共有するリスクがないことを意味します。再入可能性は、スレッドの安全性よりも強力な保証です。関数は、複数のスレッドで同時に使用できる場合、スレッドセーフです。次の場合、関数はスレッドセーフです。

  • 再入可能(つまり、呼び出し間で状態を共有しない)、または:
  • 再入不可ですが、共有状態の必要に応じて同期/ロックを使用します。

一般に、 単一UNIX仕様 および IEEE 1003.1 (つまり、「POSIX」)では、再入可能であることが保証されていない関数は、スレッドセーフであることが保証されていません。したがって、言い換えると、再入可能であることが保証されている関数のみが、マルチスレッドアプリケーションで(外部ロックなしで)移植可能に使用できます。ただし、これは、これらの標準の実装が非再入可能関数をスレッドセーフにすることを選択できないことを意味するものではありません。たとえば、Linuxはスレッドセーフ性の保証(シングルUNIX仕様の保証を超える)を追加するために、非再入可能関数に同期を頻繁に追加します。

文字列(および一般にメモリバッファ)
また、文字列/配列に根本的な欠陥があるかどうかを尋ねました。これは事実であると主張する人もいますが、私はそうではないと主張します。言語に根本的な欠陥はないのです。 CおよびC++では、配列の長さ/容量を個別に渡す必要があります(他の一部の言語のように「.length」プロパティではありません)。これは欠点ではありません。 CおよびC++開発者は、必要に応じて長さをパラメーターとして渡すだけで、正しいコードを記述できます。問題は、この情報を必要とするいくつかのAPIがパラメーターとして指定できなかったことです。または、いくつかのMAX_BUFFER_SIZE定数が使用されると仮定しました。このようなAPIは廃止され、配列/バッファ/文字列のサイズを指定できる代替APIに置き換えられました。

Scanf(最後の質問に答えて)
個人的に、私はC++ iostreamsライブラリを使用します(std :: cin、std :: cout、<<および>>演算子、std :: getline、std :: istringstream、std :: ostringstreamなど) 、それで私は通常それを扱いません。ただし、純粋なCを使用せざるを得なかった場合、個人的には fgetc() または getchar()strtol()strtoul() と組み合わせて使用​​しますなど、私は変数引数やフォーマット文字列の大ファンではないので、手動で解析します。そうは言っても、私の知る限り、 [f] scanf()[f] printf() などは、自分でフォーマット文字列を作成する限り問題ありません。任意のフォーマット文字列を渡したり、ユーザー入力をフォーマット文字列として使用したりしないでください。必要に応じて、 <inttypes.h> で定義されたフォーマットマクロを使用します。 (注: sprintf() の代わりに snprintf() を使用する必要がありますが、これは、宛先ストリングのサイズを指定せず、フォーマット文字列を使用しないことに関係しています)。また、C++では、 boost :: format は可変引数なしでprintfのようなフォーマットを提供することも指摘しておきます。

57

繰り返しになりますが、人々はマントラのように、str関数の "n"バージョンが安全なバージョンであるという馬鹿げた主張を繰り返しています。

それが彼らが意図されたものであるならば、彼らは常に文字列をヌルで終了するでしょう。

関数の「n」バージョンは、固定長フィールド(初期のファイルシステムのディレクトリエントリなど)で使用するために記述されており、文字列がフィールドに入力されない場合にのみNULターミネータが必要です。これは、関数が奇妙な副作用を持っている理由でもあります。たとえば、置換として使用しただけでは無意味に非効率的です。たとえば、strncpy()を使用します。

S2が指す配列がnバイトより短い文字列の場合、nバイトがすべてnバイトが書き込まれるまで、s1が指す配列のコピーにnullバイトが追加されます。

ファイル名を処理するために割り当てられるバッファは通常4kバイトであるため、これによりパフォーマンスが大幅に低下する可能性があります。

「想定される」安全なバージョンが必要な場合は、常に文字列を終了し、副作用のないstrlルーチン(strlcpy、strlcatなど)を取得するか、独自に作成します。ただし、これらは文字列を静かに切り捨てることができるため、実際には安全ではないことに注意してください。これは、実際のプログラムでの最善のアクションとなることはめったにありません。これが問題ない場合もありますが、それが壊滅的な結果につながる可能性のある多くの状況もあります(たとえば、処方箋を印刷するなど)。

23
Dipstick

ここでいくつかの回答は、strncat()ではなくstrcat()を使用することを提案しています。 strncat()(およびstrncpy())も回避することをお勧めします。正しく使用できず、バグにつながる問題があります。

  • strncat()への長さパラメーターは、宛先バッファーのサイズではなく、宛先にコピーできる最大文字数に関係します(ただし、正確には3番目の点を参照してください)。これにより、特に複数のアイテムが宛先に連結される場合、strncat()を本来よりも使いにくくします。
  • 結果が切り捨てられたかどうかを判断するのは難しい場合があります(これは重要な場合とそうでない場合があります)
  • 1つずれたエラーが発生するのは簡単です。 C99標準の注記にあるように、「したがって、strlen(s1)+n+1のような呼び出しの場合、s1が指す配列に含まれる可能性のある最大文字数はstrncat( s1, s2, n)です」

strncpy()には、直感的な方法で使用しようとするとバグが発生する可能性のある問題もあります。宛先がnullで終了しているとは限りません。バッファの最後の場所に'\0'を自分でドロップすることによって、そのコーナーケースを確実に処理する必要があることを確認するには(少なくとも特定の状況では)。

OpenBSDのstrlcat()strlcpy()のようなものを使用することをお勧めします(これらの機能を嫌う人もいますが、strncat() /よりも安全に使用する方がはるかに簡単だと思います。 strncpy())。

strncat()strncpy()の問題について、Todd MillerとTheo de Raadtが言ったことの一部を次に示します。

strncpy()およびstrncat()strcpy()およびstrcat()の安全なバージョンとして使用すると、いくつかの問題が発生します。どちらの関数も、経験豊富なプログラマーさえも混乱させるさまざまな非直感的な方法でNUL終了と長さパラメーターを処理します。また、切り捨てが発生したことを簡単に検出する方法もありません。 ...これらすべての問題の中で、長さパラメータによって引き起こされる混乱と、NUL終了の関連する問題が最も重要です。セキュリティホールの可能性についてOpenBSDソースツリーを監査したところ、strncpy()strncat()の悪用が蔓延しています。これらのすべてが悪用可能なセキュリティホールをもたらすわけではありませんが、安全な文字列操作でstrncpy()およびstrncat()を使用するためのルールは広く誤解されていることを明らかにしました。

OpenBSDのセキュリティ監査は、これらの機能のバグが「蔓延している」ことを発見しました。 gets()とは異なり、これらの関数canは安全に使用できますが、実際にはインターフェースがわかりにくく、直感的ではなく、正しく使用することが難しいため、多くの問題があります。マイクロソフトも分析を行ったことを知っています(ただし、彼らが公開したデータの量はわかりません)。その結果、禁止されました(または少なくとも非常に推奨されていません-「禁止」は絶対的ではないかもしれません)。 strncat()およびstrncpy()(他の関数の中でも)の使用。

詳細情報のあるリンク:

19
Michael Burr

neverを使用する必要がある標準ライブラリ関数:

setjmp.h

  • setjmp()。これらの関数はlongjmp()とともに使用すると非常に危険であると広く認識されています。これらはスパゲッティプログラミングにつながり、多数の未定義の動作を伴い、プログラム環境で意図しない副作用を引き起こす可能性があります。スタックに格納されている値に影響を与えます。参照:MISRA-C:2012ルール21.4、 CERT C MSC22-C
  • longjmp()setjmp()を参照してください。

stdio.h

  • gets()。この関数は、設計上安全ではなかったため、C言語から(C11に従って)削除されました。この関数には、C99で廃止されたフラグがすでに設定されています。代わりにfgets()を使用してください。参照:ISO 9899:2011 K.3.5.4.1、ノート404も参照してください。

stdlib.h

  • atoi()ファミリーの関数。これらにはエラー処理はありませんが、エラーが発生するたびに未定義の動作を呼び出します。関数のstrtol()ファミリで置き換えることができる完全に不要な関数。参照:MISRA-C:2012ルール21.7。

string.h

  • strncat()。しばしば誤用される厄介なインターフェースを持っています。それは主に余分な機能です。 strncpy()の備考も参照してください。
  • strncpy()。この関数の意図は、strcpy()の安全なバージョンになることではありませんでした。その唯一の目的は、常にUnixシステムで古代の文字列フォーマットを処理することであり、標準ライブラリに含まれることは既知の誤りです。この関数は、文字列をnullで終了せずに残す可能性があり、プログラマーが誤って使用することが多いため、危険です。参照: なぜstrlcpyとstrlcatは安全でないと見なされるのですか?

注意して使用する必要がある標準ライブラリ関数:

assert.h

  • assert()。オーバーヘッドが付属しているため、通常、量産コードでは使用しないでください。エラーを表示するが、必ずしもプログラム全体を閉じるわけではない、アプリケーション固有のエラーハンドラーを使用することをお勧めします。

signal.h

stdarg.h

  • va_arg()ファミリーの関数。 Cプログラムに可変長関数が存在することは、ほとんどの場合、プログラム設計が不十分であることを示しています。非常に具体的な要件がない限り、避けてください。

stdio.h
一般に、このライブラリ全体は、不適切に定義された動作や不十分な型安全性の多くのケースが伴うため、量産コードには推奨されません

  • fflush()。出力ストリームに使用するのに最適です。入力ストリームに使用すると、未定義の動作を呼び出します。
  • gets_s()。 C11の境界チェックインターフェイスに含まれているgets()の安全なバージョン。 C標準の推奨に従って、代わりにfgets()を使用することをお勧めします。参照:ISO 9899:2011 K.3.5.4.1。
  • printf()ファミリーの関数。未定義の動作が多く、型の安全性が低いリソース負荷の高い関数。 sprintf()にも脆弱性があります。これらの関数は、量産コードでは避けてください。参照:MISRA-C:2012ルール21.6。
  • scanf()ファミリーの関数。 printf()に関する備考を参照してください。また、-scanf()は、正しく使用しないと、バッファオーバーランに対して脆弱です。 fgets()は、可能な場合に使用することをお勧めします。参照: CERT C INT05-C 、MISRA-C:2012ルール21.6。
  • tmpfile()ファミリーの関数。さまざまな脆弱性の問題が付属しています。参照: CERT C FIO21-C

stdlib.h

  • malloc()ファミリーの関数。 C90のよく知られた問題に注意してください 結果をキャストしないでください 。関数のmalloc()ファミリは、独立型アプリケーションでは使用しないでください。参照:MISRA-C:2012ルール21.3。

    また、realloc()の結果で古いポインタを上書きする場合、realloc()は危険であることにも注意してください。関数が失敗した場合、リークを作成します。

  • system()。オーバーヘッドが多く、移植可能ですが、多くの場合、代わりにシステム固有のAPI関数を使用することをお勧めします。さまざまな不十分に定義された動作が付属しています。参照: CERT C ENV33-C

string.h

  • strcat()strcpy()の備考を参照してください。
  • strcpy()。コピーするデータのサイズが不明であるか、宛先バッファよりも大きい場合を除き、使用するのはまったく問題ありません。着信データサイズのチェックが行われない場合、バッファオーバーランが発生している可能性があります。これはstrcpy()自体の障害ではありませんが、呼び出し元のアプリケーションの障害ではありません-strcpy()が安全でないのは、ほとんどの場合 Microsoftによって作成された神話 です。
  • strtok()。呼び出し元の文字列を変更し、内部状態変数を使用するため、マルチスレッド環境では安全ではなくなります。
7
Lundin

一部の人々は、strcpystrcatを優先し、strncpystrncatは避けるべきであると主張します。私の意見では、これはやや主観的なものです。

ユーザー入力を処理する場合は、絶対に避けてください-間違いなくここにあります。

ユーザーから「遠い」コードでは、バッファが十分長いただ知っているの場合、strcpystrcatは、いとこに渡すnを計算する必要がないため、少し効率的かもしれません。

7
Eli Bendersky

避ける

  • strtokはスレッドセーフではないため、マルチスレッドプログラム用です。
  • getsバッファオーバーフローを引き起こす可能性があるため
6
codaddict

Microsoftの 禁止されたAPI のリストも確認してください。これらは、誤用されてセキュリティの問題につながることが多いため、Microsoftのコードから禁止されているAPI(すでにここにリストされている多くのものを含む)です。

あなたはそれらのすべてに同意することはできませんが、それらはすべて検討する価値があります。それらの誤用が多くのセキュリティバグを引き起こしたとき、彼らはリストにAPIを追加します。

5
Adrian McCarthy

strncpy()は、その名前が示唆しているstrcpy()の汎用的な置き換えではないことを再度追加する価値があります。これは、ヌルターミネータを必要としない固定長フィールド用に設計されています(元々はUNIXディレクトリエントリで使用するために設計されましたが、暗号化キーフィールドなどに役立ちます)。

ただし、strncat()の代わりにstrcpy()を使用するのは簡単です。

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

ifテストは明らかにdest_sizeは間違いなくゼロ以外です)。

5
caf

scanfを安全に使用することは非常に困難です。 scanfを適切に使用すると、バッファオーバーフローを回避できますが、要求された型に適合しない数値を読み取るときの未定義の動作に対して脆弱です。ほとんどの場合、fgetsに続けて自己解析(sscanfstrchrなどを使用)することをお勧めします。

しかし、「常にscanfを避ける」とは言いません。 scanfには用途があります。例として、10バイト長のchar配列でユーザー入力を読み取りたいとしましょう。後続の改行がある場合は削除します。ユーザーが改行の前に9文字を超える文字を入力した場合、最初の9文字をバッファーに保管し、次の改行まですべてを破棄します。できるよ:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

このイディオムに慣れると、以下よりも短くなり、いくつかの点でよりクリーンになります。

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}
2
Alok Singhal

NULで終了する文字列を処理するほとんどすべての関数は潜在的に安全ではありません。外の世界からデータを受け取り、str *()関数を介してそれを操作している場合は、大災害に備える

2
rep_movsd

Sprintfを忘れないでください-それは多くの問題の原因です。代替手段であるsnprintfには、コードの移植性を損なう可能性のある異なる実装がある場合があるため、これは真実です。

  1. linux: http://linux.die.net/man/3/snprintf

  2. ウィンドウ: http://msdn.Microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

ケース1(Linux)の場合、戻り値はバッファー全体を格納するために必要なデータの量です(指定されたバッファーのサイズよりも小さい場合、出力は切り捨てられます)。

ケース2(ウィンドウ)では、出力が切り捨てられる場合、戻り値は負の数になります。

一般に、次の機能以外は避けてください。

  1. 安全なバッファオーバーフロー(多くの機能はここで既に言及されています)

  2. スレッドセーフ/再入不可(たとえば、strtok)

各関数のマニュアルでは、安全、同期、非同期、スレッド、バッファ、バグなどのキーワードを検索する必要があります

2
INS

すべての文字列コピー/移動シナリオ-strcat()、strncat()、strcpy()、strncpy()など-いくつかの単純なヒューリスティックが適用されている場合、状況ははるかに良くなります(safer) :

1.データを追加する前に、常にバッファーをNULで満たしてください。
2.マクロ定数を使用して、文字バッファーを[SIZE + 1]として宣言します。

例:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

次のようなコードを使用できます。

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

比較的安全。コンパイル時にバッファを初期化したとしても、memset()はstrncpy()の前に表示されるはずです。これは、関数が呼び出される前に他のコードがガベージに配置されたかどうかがわからないためです。 strncpy()は、コピーされたデータを「1234567890」に切り捨て、not NULで終了します。ただし、すでにBUFSIZEではなく、バッファ全体(sizeof(Buffer))をNULで満たしているため、BUFSIZEを使用して書き込みを制限する限り、最終的に「範囲外」でNULを終了させることが保証されます。 sizeof(Buffer)の代わりに定数。

バッファとBUFSIZEも同様にsnprintf()で正常に機能します。

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

Snprintf()は特にBUFIZE-1文字のみを書き込み、その後にNULを書き込む場合でも、これは安全に機能します。したがって、バッファの最後にある余分なNULバイトを「無駄に」します...かなり小さなメモリコストで、バッファオーバーフローと未終了の文字列条件の両方を防ぎます。

Strcat()とstrncat()への私の呼び出しはもっと難しいです:それらを使用しないでください。 strcat()を安全に使用することは困難であり、strncat()のAPIは直感に反しているため、適切に使用するために必要な労力は利点を打ち消します。次のドロップインを提案します。

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

Strcat()ドロップインを作成するのは魅力的ですが、良い考えではありません。

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

targetがポインターである可能性があるためです(したがって、sizeof()は必要な情報を返しません)。コード内のstrcat()のインスタンスに対する適切な「ユニバーサル」ソリューションはありません。

「strFunc()対応」のプログラマーが頻繁に遭遇する問題は、strlen()を使用してバッファーオーバーフローから保護する試みです。内容がNULで終了することが保証されている場合、これは問題ありません。そうしないと、保護しようとしている「問題のある」コードに到達する前に、strlen()自体がバッファーオーバーランエラー(通常、セグメンテーション違反またはその他のコアダンプ状況につながる)を引き起こす可能性があります。

0
TLR