web-dev-qa-db-ja.com

符号なし整数エラーが発生しやすいのはなぜですか?

このビデオ を見ていました。 Bjarne Stroustrup は、unsigned intsがエラーを起こしやすく、バグにつながることを示しています。したがって、本当に必要な場合にのみ使用してください。また、Stack Overflowに関する質問の1つを読みました(ただし、どちらを覚えていないか)unsigned intsを使用するとセキュリティバグにつながる可能性があります。

それらはどのようにセキュリティバグにつながりますか?誰かが適切な例を挙げてそれを明確に説明できますか?

60
Destructor

考えられる側面の1つは、アンダーフローが大きな数をもたらすため、符号なし整数がループでやや見つけにくい問題を引き起こす可能性があることです。 (符号なし整数でも!)このバグのバリアントを何回作成したかは数えられません。

_for(size_t i = foo.size(); i >= 0; --i)
    ...
_

定義により、_i >= 0_は常にtrueであることに注意してください。 (最初にこれを引き起こすのは、iが署名されている場合、コンパイラはsize()の_size_t_でオーバーフローの可能性について警告することです).

言及された他の理由があります 危険-ここで使用される符号なしの型! 、私の意見では、最も強いのは、符号付きと符号なしの間の暗黙的な型変換です。

49
Ami Tavory

大きな要因の1つは、ループロジックが難しくなることです:配列の最後の要素(実際には発生します)を除くすべての要素を反復処理したいと想像してください。したがって、関数を記述します。

void fun (const std::vector<int> &vec) {
    for (std::size_t i = 0; i < vec.size() - 1; ++i)
        do_something(vec[i]);
}

よさそうですね。非常に高い警告レベルでもきれいにコンパイルできます! ( Live )これをコードに追加すると、すべてのテストがスムーズに実行され、忘れてしまいます。

さて、後で、誰かが空のvectorを関数に渡します。符号付き整数があれば、願わくば sign-compareコンパイラの警告 に気づき、適切なキャストを導入し、そもそもバグのあるコードを公開していないはずです。

しかし、符号なし整数を使用した実装では、ラップしてループ条件がi < SIZE_T_MAXになります。災害、UB、そしておそらくクラッシュ!

セキュリティバグの原因を知りたいですか?

これはセキュリティの問題でもあり、特に buffer overflow です。これを悪用する可能性のある方法の1つは、do_somethingが攻撃者が観察できることを行う場合です。彼はどの入力がdo_somethingに入力されたのかを見つけることができ、その方法では攻撃者がアクセスできないはずのデータがメモリからリークされます。これは Heartbleed bug に似たシナリオになります。 (彼の コメント で指摘してくれたラチェットフリークに感謝します。)

36
Baum mit Augen

質問に答えるためだけにビデオを見ることはありませんが、1つの問題は、符号付きの値と符号なしの値を混在させた場合に発生する可能性のある混乱した変換です。例えば:

#include <iostream>

int main() {
    unsigned n = 42;
    int i = -42;
    if (i < n) {
        std::cout << "All is well\n";
    } else {
        std::cout << "ARITHMETIC IS BROKEN!\n";
    }
}

プロモーションルールは、比較のためにiunsignedに変換され、大きな正の数と驚くべき結果が得られることを意味します。

23
Mike Seymour

「インターフェースの符号付きおよび符号なしの型」、C++レポート、1995年9月 スコット・マイヤーズによる既存の回答の変形と見なすことはできますが、特に符号なしの型を避けることが重要です- interfaces

問題は、インターフェイスのクライアントが発生する可能性のある特定のエラーを検出できなくなることです(そして、couldがエラーを発生させると、will それらを作ります)。

そこに与えられた例は次のとおりです。

_template <class T>
  class Array {
  public:
      Array(unsigned int size);
  ...
_

このクラスの可能なインスタンス化

_int f(); // f and g are functions that return
int g(); // ints; what they do is unimportant
Array<double> a(f()-g()); // array size is f()-g()
_

f()g()によって返される値の差は、非常に多くの理由で負の値になる場合があります。 Arrayクラスのコンストラクターは、この差を暗黙的にunsignedに変換される値として受け取ります。したがって、Arrayクラスの実装者として、誤って渡された_-1_の値と非常に大きな配列の割り当てを区別することはできません。

11
Marco13

Unsigned intの大きな問題は、unsigned int 0から1を引くと、結果が負の数ではなく、結果が開始した数より小さくないが、結果は可能な限り最大のunsigned int値になることです。 。

unsigned int x = 0;
unsigned int y = x - 1;

if (y > x) printf ("What a surprise! \n");

そして、これがunsigned intエラーを起こしやすいものです。もちろん、unsigned intは、動作するように設計されたとおりに動作します。あなたが何をしているかを知っていて間違いを犯さないなら、それは絶対に安全です。しかし、ほとんどの人は間違いを犯します。

優れたコンパイラーを使用している場合は、コンパイラーが生成するすべての警告をオンにして、間違いの可能性がある危険なことを行うときに通知します。

4
gnasher729

符号なし整数型の問題は、サイズに応じて、2つの異なるもののいずれかを表す場合があることです。

  1. int(例えばuint8)より小さい符号なしの型はnumbersの範囲で0..2ⁿ-1を保持し、それらの計算は範囲を超えない限り整数演算の規則に従って動作しますintタイプ。現在の規則では、そのような計算がintの範囲を超える場合、コンパイラはコードで好きなことを行うことができ、時間と因果律を否定することさえできます(一部のコンパイラは正確にそれを行います!) 、計算の結果がintより小さい符号なしの型に割り当てられる場合でも。
  2. 符号なしの型unsigned intおよびそれ以上は、mod2ⁿの合同な整数の抽象ラッピング代数リングのメンバーを保持します。つまり、計算が0..2ⁿ-1の範囲外になった場合、システムは値を範囲内に戻すために必要な2ⁿの倍数を加算または減算します。

したがって、uint32_t x=1, y=2;が与えられた場合、式x-yは、intが32ビットより大きいかどうかに応じて、2つの意味のいずれかを持ちます。

  1. intが32ビットより大きい場合、式は数値1から数値2を減算し、数値-1を生成します。 uint32_t型の変数は、intのサイズに関係なく値-1を保持できず、-1を格納すると、そのような変数は0xFFFFFFFFを保持しますが、値が符号なしの型に強制されない限り、または符号付き数量-1のように動作します。
  2. intが32ビット以下の場合、式はuint32_t値2に追加されるとuint32_t値1を生成するuint32_t値を生成します(つまり、uint32_t値0xFFFFFFFF)。

私見、CとC++が新しい符号なしの型を定義する場合、この問題はきれいに解決できます。 unum32_tおよびuwrap32_t] unum32_tは、intのサイズに関係なく、常に数値として動作します(おそらく、intが32ビットの場合、減算または単項マイナスの右辺演算を次に大きい符号付き型に昇格させる必要があります。小さい)、wrap32_tは常に代数リングのメンバーとして振る舞います(intが32ビットより大きい場合でもプロモーションをブロックします)。しかし、そのような型が存在しない場合、移植可能なコードはどこでも型強制を必要とすることが多いため、移植可能でクリーンなコードを書くことはしばしば不可能です。

2
supercat

CおよびC++の数値変換規則はビザンチンの混乱です。符号なしの型を使用すると、純粋に符号付きの型を使用するよりもはるかに大きな混乱にさらされます。

たとえば、一方が符号付きでもう一方が符号なしの2つの変数を比較する単純なケースを考えてみましょう。

  • 両方のオペランドがintより小さい場合、それらは両方ともintに変換され、比較により数値的に正しい結果が得られます。
  • 符号なしのオペランドが符号付きのオペランドよりも小さい場合、両方とも符号付きのオペランドの型に変換され、比較により数値的に正しい結果が得られます。
  • 符号なしのオペランドのサイズが符号付きのオペランド以上で、サイズもint以上の場合、両方とも符号なしのオペランドの型に変換されます。符号付きオペランドの値がゼロより小さい場合、これは数値的に不正確な結果につながります。

別の例として、同じサイズの2つの符号なし整数を乗算することを検討してください。

  • オペランドのサイズがintのサイズ以上の場合、乗算ではラップアラウンドセマンティクスが定義されます。
  • オペランドのサイズがintより小さいが、intのサイズの半分以上の場合、未定義の動作が発生する可能性があります。
  • オペランドのサイズがintのサイズの半分より小さい場合、乗算により数値的に正しい結果が生成されます。この結果を元の符号なし型の変数に割り当てると、定義されたラップアラウンドセマンティクスが生成されます。
2
plugwash