これは この質問 と、strncpy
はCでは非常に安全な文字列処理関数ではなく、到達するまでゼロを埋めることを学んだという特定の回答に対するコメントに触発されています。 n
、私が知らなかった何か。
具体的には、引用するには R ..
strncpyはヌル終了せず、宛先バッファの残り全体をヌルパディングします。これは時間の大きな無駄です。独自のヌルパディングを追加することで前者を回避できますが、後者は回避できません。 「安全な文字列処理」機能としての使用を目的としたものではなく、Unixディレクトリテーブルおよびデータベースファイルの固定サイズのフィールドを操作するためのものでした。 snprintf(dest、n、 "%s"、src)は、標準Cで唯一正しい「安全なstrcpy」ですが、かなり遅くなる可能性があります。ちなみに、切り捨て自体が大きなバグになる可能性があり、場合によっては特権の昇格やDoSにつながる可能性があるため、問題が発生したときに出力を切り捨てる「安全な」文字列関数をスローしても、「安全」または「安全」。代わりに、宛先バッファーが適切なサイズであることを確認し、単にstrcpy(または、ソース文字列の長さがわかっている場合はmemcpy)を使用する必要があります。
そして ジョナサンレフラー から
Strncat()は、strncpy()よりもインターフェイスがさらに混乱していることに注意してください。この長さの引数は正確には何ですか? strncpy()などを指定したものに基づいて期待するものではないため、strncpy()よりもエラーが発生しやすくなります。文字列をコピーする場合、事前にすべてのサイズを常に把握し、事前に十分なスペースがあることを確認するため、memmove()のみが必要であるという強い議論があるという意見がますます高まっています。 strcpy()、strcat()、strncpy()、strncat()、memcpy()のいずれよりも優先してmemmove()を使用します。
ですから、私は明らかにC標準ライブラリに少し錆びています。したがって、私は質問を提起したいと思います:
どのC標準ライブラリ関数が不適切に/セキュリティ問題/コードの欠陥/非効率を引き起こす/引き起こす可能性のある方法で使用されていますか?
客観性のために、私は答えのためのいくつかの基準を持っています:
避けてください:
これは主観的であると見なされる可能性が高く、明確な答えがないため、すぐにコミュニティwikiにフラグを立てます。
私もC99に従って働いています。
strtok()
関数の一般的な落とし穴は、実際には区切り文字を_'\0'
_に置き換えながら、解析された文字列が変更されないままであると想定することです。
また、strtok()
は、文字列全体がトークン化されるまで、後続の呼び出しを行うことによって使用されます。一部のライブラリ実装は、strtok()
の内部ステータスをグローバル変数に格納します。これにより、strtok()
が複数のスレッドから同時に呼び出されると、厄介な驚きが生じる可能性があります。
CERT C Secure Coding Standard は、あなたが尋ねたこれらの落とし穴の多くをリストしています。
どのC標準ライブラリ関数が不適切に/セキュリティ問題/コードの欠陥/非効率を引き起こす/引き起こす可能性のある方法で使用されていますか?
私は明白なことで行くつもりです:
char *gets(char *s);
それを適切に使用することは単に不可能であるというその驚くべき特殊性で。
ほとんどの場合、atoi()
は使用しないでください(これはatof()
、atol()
、およびatoll()
にも適用されます)。
これは、これらの関数が範囲外のエラーをまったく検出しないためです。標準では、単に「結果の値を表現できない場合、動作は定義されていません。」と記載されています。したがって、安全に使用できるのは、入力が確実に範囲内にあることを証明できる場合のみです(たとえば、長さ4以下の文字列をatoi()
に渡す場合、範囲外になることはありません。範囲)。
代わりに、strtol()
ファミリーの関数の1つを使用してください。
質問をより広い意味でのインターフェースに拡張しましょう。
errno
:
技術的には、それが何であるか、変数、マクロ、暗黙の関数呼び出しでさえ明確ではありませんか?最近のシステムでは、ほとんどの場合、スレッド固有のエラー状態を持つ関数呼び出しに変換されるマクロです。それは悪です:
今後の標準では、errno
の定義がもう少しわかりやすくなっていますが、これらの醜さは残っています。
多くの場合、strtok_rがあります。
Reallocの場合、古いポインターを使用する必要がある場合、別の変数を使用することはそれほど難しくありません。プログラムが割り当てエラーで失敗した場合、古いポインタをクリーンアップする必要はほとんどありません。
printf
とscanf
をこのリストのかなり上位に配置します。書式設定指定子を正確に取得する必要があるという事実により、これらの関数は使いにくく、間違いが非常に簡単です。また、データを読み取るときにバッファオーバーランを回避することは非常に困難です。さらに、「printf形式の文字列の脆弱性」は、善意のプログラマーがクライアント指定の文字列をprintfの最初の引数として指定した場合に、おそらく数え切れないほどのセキュリティホールを引き起こしました。
一般的にmalloc
ファミリーはどうですか?私が見た大規模で長寿命のプログラムの大部分は、あたかも無料であるかのように、至る所で動的メモリ割り当てを使用しています。もちろん、リアルタイム開発者はこれが神話であることを知っており、動的割り当てを不注意に使用すると、メモリ使用量が壊滅的に爆発したり、アドレス空間が断片化してメモリが枯渇する可能性があります。
マシンレベルのポインタを持たない一部の高級言語では、実装がオブジェクトへの参照を最新に保つことができる限り、プログラムの存続期間中にオブジェクトを移動してメモリを最適化できるため、動的割り当てはそれほど悪くありません。非従来型のC実装でもこれを行うことができますが、詳細を理解することは簡単ではなく、すべてのポインター逆参照で非常に大きなコストがかかり、ポインターがかなり大きくなるため、実用的な目的では、Cでは不可能です。
私の疑惑は、正しい解決策は通常、長寿命のプログラムがmalloc
を使用して通常どおり小さなルーチン割り当てを実行することですが、大規模で長寿命のデータ構造を再構築および置換できる形式で保持することです。断片化と戦うために定期的に、またはアプリケーション内の単一の大きなデータ単位を構成する多数の構造を含む大きなmalloc
ブロック(ブラウザでのWebページのプレゼンテーション全体など)、またはディスク上の固定サイズのメモリ内キャッシュまたはメモリマップファイル。
realloc
についてはすでに1つの答えがありますが、私はそれについて別の見方をしています。多くの場合、人々がrealloc
を意味するときにfree
と書くのを見てきました。 malloc
-言い換えると、新しいデータを保存する前にサイズを変更する必要があるゴミでいっぱいのバッファがある場合。もちろん、これは、上書きされようとしているゴミの潜在的に大きな、キャッシュスラッシングmemcpy
につながります。
増大するデータで正しく使用された場合(オブジェクトをサイズn
に増大させるための最悪の場合のO(n^2)
パフォーマンスを回避する方法で、つまり、スペースが不足したときに線形ではなく幾何学的にバッファーを増大させます)、realloc
は、単に独自の新しいmalloc
、memcpy
、およびfree
サイクルを実行するよりも疑わしい利点があります。 realloc
がこれを内部的に回避できる唯一の方法は、ヒープの上部にある単一のオブジェクトを操作している場合です。
新しいオブジェクトをcalloc
でゼロ塗りつぶしたい場合、realloc
が新しい部分をゼロ塗りつぶさないことを忘れがちです。
そして最後に、realloc
のもう1つの一般的な使用法は、必要以上に割り当ててから、割り当てられたオブジェクトのサイズを必要なサイズに縮小することです。ただし、これは、チャンクをサイズで厳密に分離する実装では実際に有害である可能性があり(追加の割り当てとmemcpy
)、場合によっては(大きな空きチャンクの一部を分割して新しい小さなオブジェクトを格納することにより)断片化が増える可能性があります。既存の小さな空きチャンクを使用する代わりに)。
realloc
奨励悪い習慣と言うかどうかはわかりませんが、それは私が注意したい機能です。
私のbêtesnoireの1つは strtok()
です。これは、再入可能ではなく、処理中の文字列をハッキングして、分離する各トークンの最後にNULを挿入するためです。これに伴う問題は軍団です。それはしばしば問題の解決策として悲惨なほど宣伝されますが、それ自体が問題であることがよくあります。常にではありません-安全に使用できます。しかし、あなたが注意している場合に限ります。安全に使用できないgets()
を除いて、ほとんどの関数に同じことが当てはまります。
gmtime()
やlocaltime()
など、グローバル状態を操作する関数。これらの関数は、複数のスレッドで安全に使用することはできません。
EDIT:Rand()
は同じカテゴリにあります。少なくともスレッドセーフの保証はありません。私のLinuxシステムでは、マニュアルページに再入可能ではなくスレッドセーフではないと警告されています。
まったく別の方法で、atan()
がある場合のatan2()
の利点を実際に理解したことはありません。違いは、atan2()
が2つの引数を取り、-π.. +πの範囲内の任意の角度を返すことです。さらに、ゼロ除算エラーと精度エラーの損失(非常に小さい数を非常に大きい数で除算する、またはその逆)を回避します。対照的に、atan()
関数は-π/ 2 .. +π/ 2の範囲の値のみを返すため、事前に除算を行う必要があります(atan()
のシナリオは思い出せません。 ()] _は、単にアークタンジェントのテーブルを生成する以外に、除算なしで使用できます)。単純な値が与えられたときにatan2()
の約数として1.0を提供しても、限界を押し上げることにはなりません。
別の答え、これらは実際には関連していないので、Rand
:
この関数のいくつかは、いくつかのグローバル状態を変更しています。 (Windowsの場合)この状態は単一のスレッドごとに共有されます-予期しない結果が生じる可能性があります。たとえば、すべてのスレッドでRand
を最初に呼び出すと同じ結果が得られ、疑似ランダムにするために注意が必要ですが、(デバッグ目的で)決定論的です。