94

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

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

それらを安全に使用できますか?良い習慣はありますか?

4

13 に答える 13

59

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

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


再入可能 他の例には、非再入可能であり(したがって、スレッドセーフであることが保証されていない) gethostbyaddr()およびgethostbyname()が含まれ、再入可能getaddrinfo()およびfreeaddrinfo()に置き換えられています。

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

時代遅れで
移植性がない他のいくつかの機能は、機能を複製し、他のバリアントほど移植性がないため、単に非推奨になります。たとえば、bzero()は廃止され、memset()が優先されます。

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

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

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

文字列(および一般的にはメモリバッファ)
また、文字列/配列に根本的な欠陥があるかどうかを尋ねました。これが事実であると主張する人もいるかもしれませんが、私は、いいえ、言語に根本的な欠陥はないと主張します。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()などと組み合わせて使用​​し、手動で解析します。 varargsまたはフォーマット文字列。とはいえ、私の知る限り、[f] scanf()[f] printf()には問題はありません。、など。自分でフォーマット文字列を作成する限り、任意のフォーマット文字列を渡したり、ユーザー入力をフォーマット文字列として使用したりすることはできません。また、必要に応じて<inttypes.h>で定義されたフォーマットマクロを使用します。(sprintf()の代わりにsnprintf()を使用する必要がありますが、これは、フォーマット文字列の使用ではなく、宛先バッファーのサイズの指定に失敗したことと関係があります)。また、C ++では、 boost::formatがvarargsなしでprintfのようなフォーマットを提供することも指摘しておく必要があります。

于 2010-04-02T08:27:09.720 に答える
24

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

それが意図されたものである場合、それらは常に文字列を null で終了します。

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

s2 が指す配列が n バイトより短い文字列の場合、s1 が指す配列のコピーにヌル バイトが追加され、合計で n バイトが書き込まれます。

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

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

于 2010-04-02T09:34:14.333 に答える
20

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

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

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

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

以下は、Todd Miller と Theo de Raadt がstrncat()andの問題について語ったことの一部strncpy()です:

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

OpenBSD のセキュリティ監査では、これらの機能のバグが「はびこっている」ことが判明しました。とは異なりgets()、これらの機能は安全に使用できますが、実際にはインターフェイスがわかりにくく、直感的ではなく、正しく使用するのが難しいため、多くの問題があります。Microsoft も分析を行っていることを知っています (ただし、彼らが公開したデータの量はわかりません)。strncat()およびstrncpy()(他の関数の中でも) の使用。

詳細情報へのリンク:

于 2010-04-02T09:02:06.387 に答える
7

と を支持して、とを避けるべきだstrcpyと主張する人もいます。私の意見では、これはやや主観的です。strcatstrncpystrncat

ユーザー入力を扱うときは、絶対に避けるべきです-ここでは間違いありません。

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

于 2010-04-02T08:29:23.043 に答える
6

避ける

  • strtokマルチスレッドプログラムの場合、スレッドセーフではありません。
  • getsバッファオーバーフローを引き起こす可能性があるため
于 2010-04-02T08:26:39.207 に答える
5

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

それらすべてに同意することはできないかもしれませんが、それらはすべて検討する価値があります。API の誤用によって多数のセキュリティ バグが発生した場合、API をリストに追加します。

于 2010-04-02T22:31:25.547 に答える
5

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

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

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

(このif検定は、明らかにdest_size非ゼロであることがわかっている一般的なケースでは省略できます)。

于 2010-04-02T11:04:23.930 に答える
3

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

scanfしかし、私は「常に 避ける」とは言いません。scanf用途があります。char例として、 10 バイト長の配列でユーザー入力を読み取りたいとします。末尾の改行がある場合は削除します。ユーザーが改行の前に 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;
    }
}
于 2010-04-02T09:42:11.437 に答える
2

sprintf のことを忘れないでください。これは多くの問題の原因です。これは、代替の snprintf の実装が異なる場合があり、コードの移植性が失われる可能性があるためです。

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

  2. Windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

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

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

通常、次のような関数は避ける必要があります。

  1. バッファ オーバーフロー セーフ (多くの関数は既にここで言及されています)

  2. スレッドセーフ/再入不可 (strtok など)

各関数のマニュアルで、safe、sync、async、thread、buffer、bugs などのキーワードを検索する必要があります。

于 2010-04-02T09:02:36.320 に答える
2

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

于 2010-04-02T08:29:07.573 に答える
0

すべての文字列コピー/移動シナリオ - strcat()、strncat()、strcpy()、strncpy() など -いくつかの単純なヒューリスティックが適用されると、事態ははるかに良く (より安全

に) 進みます:    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");

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

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 が続きますが、これは安全に機能します。そのため、Buffer の末尾にある余分な 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() 自体がバッファ オーバーラン エラーを引き起こす可能性があります (通常、セグメンテーション違反またはその他のコア ダンプの状況につながります)。

于 2014-06-10T22:35:39.050 に答える
-2

atoi はスレッドセーフではありません。マニュアルページの推奨に従って、代わりに strtol を使用します。

于 2010-04-02T16:11:37.753 に答える