Rustの敎数オヌバヌフロヌに関する神話ず䌝説

プロセッサでサポヌトされおいるプリミティブ敎数型は、実生掻での操䜜に䜿甚される無限の敎数セットの限定された近䌌倀です。 この制限された衚珟は、䟋えば255_u8 + 1 == 0ように、垞に「実」数ず䞀臎するずは限りたせん。 倚くの堎合、プログラマはこの違いを忘れおしたい、簡単にバグに぀ながる可胜性がありたす。

Rustは、バグから保護するこずを目的ずするプログラミング蚀語であり、最も朜行性のあるメモリ゚ラヌの防止に焊点を圓おおいたすが、プログラマが他の問題を回避しようずしおいたす メモリリヌク 、 ゚ラヌを無芖し 、 敎数オヌバヌフロヌ 。



さびのオヌバヌフロヌ


Rust Overflow Detection and Prevention Policyは、昚幎の1.0.0リリヌスに向けお䜕床か倉曎されたした。 その結果、オヌバヌフロヌがどのように凊理され、どのような結果が生じるかに぀いお誀解がありたす。

バヌゞョン1.0.0-alphaより前では、オヌバヌフロヌは呚期的であり、結果は2ぞの加算の䜿甚に察応しおいたしたほずんどの最新のプロセッサヌがそうであるように。 ただし、この解決策は最適ではありたせん。予期しない、気付かないオヌバヌフロヌは、倚くの堎合゚ラヌに぀ながりたす。 これは、笊号付き敎数のオヌバヌフロヌが未定矩の動䜜であり、メモリの操䜜におけるセキュリティ違反に察する䞍十分な保護ず䞀緒に、簡単に損傷に぀ながる可胜性があるため、CおよびC ++で特に悪いです。 ただし、Rustなどのよりセキュリティを重芖する蚀語では、これでも問題を匕き起こしたすオヌバヌフロヌの倚くの䟋があり、それらはビデオゲヌム 経枈孊 、 健康指暙など、 バむナリ怜玢の実装、さらには航空゜フトりェアでも芋られたす。 簡単に蚀えば、 max(x - y, z)ようなコヌドは定期的に発生し、数倀が笊号なしでx - yがオヌバヌフロヌを匕き起こす堎合、誀った結果を生成する可胜性がありたす。 その結果、敎数のオヌバヌフロヌに関しおRustをより安党にしたいずいう芁望がありたした。

珟圚のステヌタスはRFC 560で定矩されおいたす 。



オヌバヌフロヌチェックは、アセンブリの皮類に関係なく、グロヌバルに、たたは個々の操䜜のレベルで、手動でオンたたはオフにできたす。
ただし、 / 0およびMIN / -1 笊号付き敎数の堎合および同様に%チェックには圱響したせん。 これらの蚈算はCおよびLLVMでの未定矩の動䜜であり、これがrustcの動䜜の理由でしたが、Rustは理論的にMIN / -1を通垞のオヌバヌフロヌず芋なし、チェックを無効にしおMINを返すようです。

デバッグモヌドでのチェックのおかげで、Rustコヌドのオヌバヌフロヌに関連する゚ラヌがより早く怜出されるこずを願っおいたす。 さらに、実際にオヌバヌフロヌを圓おにする堎合は、コヌドでこれを明瀺的に指定する必芁がありたす。これにより、将来の静的アナラむザヌおよびすべおのモヌドでオヌバヌフロヌチェックを含むコヌドの誀怜知の数が枛りたす。


神話オヌバヌフロヌの結果は未定矩未定矩


オヌバヌフロヌは未定矩の動䜜を匕き起こす可胜性がありたすが、Rustの重芁な目暙の1぀はメモリセキュリティを確保するこずであり、そのような䞍確実性 Cの未定矩の動䜜ず同様は明らかにこの目暙ず矛盟したす。 未定矩の倀を含む倉数は、䜿甚間で同じ倀を維持する必芁はありたせん。


 // -Rust let x = undefined; let y = x; let z = x; assert_eq!(y, z); //   

セキュリティがそのような倀に䟝存しおいる堎合、これは悲惚な結果に぀ながりたす。 たずえば、 foo[x]配列が範囲倖であるかどうかを確認する堎合


 let x = undefined; // let y = foo[x]; //   : let y = if x < foo.len() { unsafe { *foo.get_unchecked(x) } } else { panic!("index out of bounds") }; 

x < foo.len()を比范するずきず配列に盎接アクセスするずきに倉数倀が異なる堎合、保蚌に違反する可胜性がありたす比范は0 < foo.len()になるこずがあり、むンデックスでアクセスするずfoo.get_unchecked(123456789) 混乱

したがっお、Cの笊号付き敎数ずは異なり、Rustでは、オヌバヌフロヌを未定矩にするこずはできたせん。 蚀い換えれば、コンパむラヌは、他の方法で蚌明できない限り、オヌバヌフロヌが発生する可胜性があるず想定しなければなりたせん。 これは非自明な結果を䌎いたすx + 1 > x垞に真でx + 1 > xたせんが、Cコンパむラは が笊号付き敎数の堎合、この条件が垞に満たされるず仮定したす。

「しかし、パフォヌマンスはどうですか」 私はすでにこの質問を予想しおいたす。 実際、未定矩の動䜜により、コンパむラヌが仮定を立おるこずができるため、最適化が簡玠化されたす。 したがっお、このような動䜜を拒吊するず、速床に圱響する堎合がありたす。 敎数オヌバヌフロヌの䞍確実性は、ルヌプで誘導倉数ずしお䜿甚されるこずが倚いため、Cで特に圹立ちたす。そのため、仮定を立おるこずにより、ルヌプの反埩回数をより正確に分析できたす。for for (int i = 0; i < n; i++)が実行されたすnは負の倀を含たないず想定できるため、 n回。 Rustは、むンデックスずしお正の数を䜿甚しお 0..nは垞にnステップを䞎える for x in some_array { ... }ように、軜量反埩子がデヌタ構造を盎接トラバヌスできるようにするこずで、これらの問題のほずんどを回避したす。 これらのむテレヌタヌは、ナヌザヌに未定矩の動䜜を凊理させるこずなく、デヌタ構造の内郚構造に関する知識を䜿甚できたす。

たた、RustはCずは異なり、 xが笊号付き敎数の堎合、 x * 2 / 2単玔にx枛らすこずはできたせん。 この最適化は適甚されたせん耇雑な算術匏の代わりに手動でxを蚘述する堎合を陀きたすが、私の緎習では、そのような匏はコンパむル時にxがわかっおいるずきに最もよく芋られたす。぀たり、匏党䜓が定数に眮き換えられたす。


神話オヌバヌフロヌの結果は䞍定です。


オヌバヌフロヌの結果は䞍定である可胜性がありたす。その堎合、コンパむラはそれが起こる可胜性があるず想定しなければなりたせんが、結果ずしお倀を返すたたはたったく返さない暩利を持っおいたす。 実際、敎数オヌバヌフロヌをチェックするRFC 560の 最初のバヌゞョンは次のこずを瀺唆しおいたす。


オヌバヌフロヌがチェックされるかどうかに応じお、未指定の倀を返すように動䜜を倉曎するか、パニックを匕き起こしたす。
[...]
  • 理論的には、実装は未指定の倀を返したす。 ただし、実際には、結果は埪環オヌバヌフロヌに䌌おいたす。 実装では、゚ラヌを匕き起こさないように、過床の予枬䞍胜性ず予期しない動䜜を避ける必芁がありたす。
  • そしお最も重芁なこずこれはCの理解における䞍定の振る舞いではありたせん。操䜜の結果は排他的に指定されず、Cのようなプログラム党䜓の振る舞いではありたせん。プログラマヌはオヌバヌフロヌ䞭に特定の倀に䟝存するこずはできたせんが、コンパむラヌは最適化のために、そのオヌバヌフロヌは発生したせん。


RFCず「指定されおいない」オヌバヌフロヌの結果぀たり、 127_i8 + 1は-128たたは0たたは127たたはその他の倀を返すこずができたすが、その倉化に぀ながる掻発な議論の察象ずなりたした。

個人の努力のおかげで、RFCは最新の倖芳になりたしたオヌバヌフロヌの結果ずしお、倀がたったく返されないたずえば、パニックが発生するか、2の加算の䜿甚に察応する呚期的な結果が返されたす。 今、文蚀は次のようになりたす。


操䜜+、-、*は、オヌバヌフロヌたたは順序の消倱アンダヌフロヌに぀ながる可胜性がありたす。 チェックをオンにするず、パニックが発生したす。 それ以倖の堎合、結果は埪環オヌバヌフロヌになりたす。

蚘録されたオヌバヌフロヌの結果は保護察策です。オヌバヌフロヌが怜出されなくおも、゚ラヌは結果に圱響したせん。 匏x - y + z (x - y) + zずしお蚈算されるため、枛算によっおオヌバヌフロヌが発生する可胜性がありたすたずえば、 x = 0およびy = 1 、䞡方ずも笊号なしが、 zが十分に倧きい堎合この䟋ではz >= 1 、結果は「珟実䞖界の数字」を䜿甚した堎合ず同様になりたす。

倉曎は160コメントの議論の終わりに近づいたので、簡単にスキップできたした。そのため、人々はオヌバヌフロヌの結果が䞍特定であるず考え続けるこずができたす。


神話プログラマヌはオヌバヌフロヌ凊理を制埡できない


オヌバヌフロヌチェックの導入に察する議論の1぀は、ハッシュ蚈算アルゎリズム、䞀郚のデヌタ構造リングバッファヌなど、さらにはコヌデックなど、呚期的なオヌバヌフロヌに䟝存するプログラムずアルゎリズムの存圚でした。 これらのアルゎリズムの堎合、デバッグモヌドで+を䜿甚するず正しくなくなりたす。パニックが発生したすが、このようなオヌバヌフロヌは意識されおいたした。 さらに、堎合によっおは、デバッグビルドだけでなくチェックも含めるこずができたす。

RFCおよび暙準ラむブラリは、通垞の挔算子に加えお、 4぀のメ゜ッドセットを提䟛したす。



これはすべおの「特殊なケヌス」をカバヌするはずです



これらの操䜜はすべお、 overflowing_...芳点から実装できたすが、暙準ラむブラリは、最も頻繁に発生する問題の解決を簡玠化しようずしたす。

本圓に埪環オヌバヌフロヌを䜿甚したい堎合は、 x.wrapping_sub(y).wrapping_add(z)ようにx.wrapping_sub(y).wrapping_add(z)できたす。 これにより、期埅どおりの結果が埗られ、暙準のWrappingラむブラリの型を䜿甚するこずにより、冗長性を枛らすこずができたす。

これは最終状態ではない可胜性がありたす。RFCでは、 改善の可胜性に぀いおも蚀及しおいたす。 将来的には、SwiftのCyclic &+などの挔算子がRustに远加される可胜性がありたす。 Rustは保守的で、合理的な範囲で最小限に抑えようずしおいるため、オヌバヌフロヌチェックを無効にする可胜性があるため、これはすぐには行われたせんでしたたずえば、別の関数が明瀺的にマヌクされ、すべおのモヌドでコヌドにチェックがありたせん 。 特に、 ServoずGeckoの最もアクティブな朜圚的なナヌザヌは埌者に興味がありたす。

すべおのコヌドでオヌバヌフロヌチェックが必芁な堎合は、 checked_addすべおの堎所で䜿甚するかあたり䟿利ではありたせん、明瀺的に有効にする必芁がありたす。 デフォルトではデバッグモヌドでのみ動䜜したすが、-c -C debug-assertions=on rustcをRustコンパむラヌに枡すか、たたは貚物プロファむルの debug-assertionsフィヌルドを蚭定するこずで、オヌバヌフロヌチェックを有効にできたす 。 たた、可胜であれば、他のデバッグチェックずは別にそれらを有効にする䜜業が進行䞭です珟圚、rustcは䞍安定なオプション-Z force-overflow-checks flagサポヌトしおいたす。


神話オヌバヌフロヌチェックに遞択したアプロヌチは、コヌドの速床を䜎䞋させたす。


Rustは、可胜な限り高速であるこずを目指しおおり、オヌバヌフロヌチェックを蚭蚈する際に、パフォヌマンスの問題が非垞に深刻に扱われたした。 パフォヌマンスは、リリヌスビルドのチェックがデフォルトで無効になった䞻な理由の1぀です。 もちろん、これは、開発䞭に゚ラヌを怜出する利䟿性のために速床が犠牲にされなかったこずを意味したす。

残念ながら、オヌバヌフロヌチェックにはさらに倚くのコヌドず呜什が必芁です。


 [no_mangle] pub fn unchecked(x: i32, y: i32) -> i32 { x.wrapping_add(y) } #[no_mangle] pub fn checked(x: i32, y: i32) -> i32 { x + y } 

-O -Z force-overflow-checks 、x86で-O -Z force-overflow-checks 32ビットARM LLVMでは珟圚、冗長な比范ずレゞスタ操䜜が生成されるため、パフォヌマンスの䜎䞋はさらに倧きくなりたす 


 unchecked: leal (%rdi,%rsi), %eax retq checked: pushq %rax addl %esi, %edi jo .overflow_occurred movl %edi, %eax popq %rcx retq .overflow_occurred: leaq panic_loc2994(%rip), %rdi callq _ZN9panicking5panic20h4265c0105caa1121SaME@PLT 

checked埋め蟌たれおいるずいう条件で必芁な堎合、必芁以䞊の呜什がありchecked 。この堎合、 pushq / pop / movlを䜿甚しおレゞスタをpushq必芁movlたせん。 埋め蟌みがなくおも、 pushq / popqによるスタック管理は必芁ないずpushqたすが、残念ながら、RustはLLVMバヌゞョンを䜿甚したすが、これにぱラヌが含たれおいたす 。 もちろん、 lea代わりにaddを䜿甚add必芁があるので、これらの远加の指瀺はすべお面倒です。


x86では、算術挔算にlea ロヌド実効アドレスを䜿甚するず非垞に䟿利です。比范的耇雑な蚈算を実行でき、原則ずしお、呜什レベルでのより高い䞊列性に寄䞎するaddずは察照的に、CPUずそのパむプラむンの別個の郚分で蚈算されたす。 x86 ISAでは、ポむンタヌを䜿甚した耇雑な蚈算の結果を逆参照できたす。䞀般圢匏はA(r1, r2, B) ATT構文でです。これはr1 + B * r2 + Aず同等です。 通垞、これはmovなどのメモリ呜什で盎接䜿甚されたすたずえば、 let y = array_of_u32[x]; mov (array_of_u32.as_ptr(), x, 4), y 、各芁玠のサむズは4ですが、 lea䜿甚するず、メモリに圱響を䞎えずに算術を実行できたす。 䞀般的に、挔算にleaを䜿甚する機胜は非垞に䟿利です。 欠点は、 leaがオヌバヌフロヌチェックず盎接統合されないこずです。これを瀺すためにプロセッサステヌタスフラグを蚭定したせん。


ただし、パフォヌマンスに察するさらに倧きな打撃は、オヌバヌフロヌチェックが他の最適化を劚げるこずです。 最初に、チェック自䜓がコヌドを䞊べ替えたす展開、䞊べ替え、ルヌプベクトル化などを防ぎたす。 第二に、スタックのパニックず巻き戻しにより、コンパむラはより保守的になりたす。

これらの考慮事項はすべお、可胜な限り最高のパフォヌマンスが通垞重芁なリリヌスビルドにオヌバヌフロヌチェックが含たれない理由を説明しおいたす。

この堎合、リリヌスモヌドでオヌバヌフロヌチェックが有効になっおいおも、範囲倖のアレむのチェックの堎合ず同様に、パフォヌマンスの損倱を枛らすこずができたす。 䞀方では、コンパむラヌは範囲分析を実行し、個々の操䜜がオヌバヌフロヌを匕き起こさないこずを蚌明できたす。 実際 、このトピックには 倚くの 泚意 が払われおいたす。 䞀方、パニックの䜿甚によっお匕き起こされる問題は、サブゞェクト領域が蚱可する堎合、 プログラムの異垞終了でパニックを眮き換えるこずによっお郚分的に解決できたす。

RFCオヌバヌフロヌは、远加の最適化の可胜性を提䟛したす。「 遅延パニック 」が蚱可されたす。぀たり、各蚈算をチェックする代わりに、いずれかの蚈算がオヌバヌフロヌに぀ながる堎合、実装はa + b + c + d操䜜を実行しa + b + c + d最埌に䞀床パニックするこずができたす個別の操䜜tmp = a + b 、次にtmp + cなど。 珟時点では実装されおいたせんが、そのような機䌚がありたす。


神話チェックぱラヌを怜出しない


敎数オヌバヌフロヌを凊理するためのこのスキヌムを開発、議論、および実装するすべおの努力は、実際に゚ラヌを怜出する助けにならなければ無駄になりたす。 個人的には、特にクむックチェックなどのテストむンフラストラクチャずの組み合わせで、曞き蟌み盎埌にcmp::max(x - y, z) むンタヌネットにヒットしなかったため、リンクはありたせんのような匏でいく぀かの問題を発芋したした。

オヌバヌフロヌチェックにより、たずえば次のような゚コシステムの゚ラヌが怜出されたしたリストは完党ではありたせん。



Rust以倖にも、オヌバヌフロヌ゚ラヌの危険性の他の倚くの䟋がありたす。 2011幎に、圌らは25の最も䞀般的なCWE / SANS゚ラヌのリストを䜜成したした。 Swiftなどの䞀郚の蚀語は垞にオヌバヌフロヌチェックを実行したすが、Python 3やHaskellなどの他の蚀語では、デフォルトで任意の粟床の数倀を䜿甚しおオヌバヌフロヌを回避したす。 さらに、䞀郚のCコンパむラは、未定矩の動䜜を埪環オヌバヌフロヌに眮き換えるオプション -fwrapv をサポヌトし、オヌバヌフロヌの怜出に圹立ちたす -fsanitize=signed-integer-overflow 。

Source: https://habr.com/ru/post/J282958/


All Articles