std :: ptr :: writeは、書き込むバイトの「初期化されていない状態」を転送しますか?


8

FFIの境界を越えてポインターサイズのintに収まるトランザクション型を支援するライブラリを開発しています。次のような構造体があるとします。

use std::mem::{size_of, align_of};

struct PaddingDemo {
    data: u8,
    force_pad: [usize; 0]
}

assert_eq!(size_of::<PaddingDemo>(), size_of::<usize>());
assert_eq!(align_of::<PaddingDemo>(), align_of::<usize>());

この構造体には、1データバイトと7パディングバイトがあります。この構造体のインスタンスをaにパックしusize、FFI境界の反対側でアンパックします。このライブラリーは汎用なので、私はMaybeUninitand を使用していptr::writeます:

use std::ptr;
use std::mem::MaybeUninit;

let data = PaddingDemo { data: 12, force_pad: [] };

// In order to ensure all the bytes are initialized,
// zero-initialize the buffer
let mut packed: MaybeUninit<usize> = MaybeUninit::zeroed();
let ptr = packed.as_mut_ptr() as *mut PaddingDemo;

let packed_int = unsafe {
    std::ptr::write(ptr, data);
    packed.assume_init()
};

// Attempt to trigger UB in Miri by reading the
// possibly uninitialized bytes
let copied = unsafe { ptr::read(&packed_int) };

そのassume_init呼び出しは未定義の動作を引き起こしましたか?つまり、ptr::writeが構造体をバッファにコピーするとき、パディングバイトの初期化されていない状態をコピーして、初期化された状態をゼロバイトとして上書きしますか?

現在、このコードまたは類似のコードをMiriで実行すると、未定義の動作は検出されません。ただし、githubでのこの問題に関する説明に従って、ptr::writeこれらのパディングバイトをコピーすること、さらにはそれらの初期化されていないことをコピーすることが許可されていると考えられます。本当?のドキュメントでptr::writeは、これについてはまったく触れられておらず、初期化されていないメモリに関するノミコンのセクションについても触れていません。


一部の有用な最適化は、不確定な値のコピーを使用して宛先を不確定な状態のままにすることで容易になりますが、不確定だった元の部分がどのようなセマンティクスでオブジェクトをコピーできるようにする必要がある場合もあります。コピーでは指定されていません(したがって、今後のコピーは互いに一致することが保証されます)。残念ながら、言語設計者は、セキュリティの影響を受けやすいコードで後者のセマンティクスを実現できることの重要性をあまり考慮していないようです。
スーパーキャット

回答:


3

そのassume_init呼び出しは未定義の動作を引き起こしましたか?

はい。「初期化されていない」は、Rust Abstract Machineの1バイトが持つことができるもう1つの値であり、通常の0x00〜0xFFの隣にあります。この特別なバイトを0xUUとして書き込みましょう。(この件の背景については、このブログ投稿を参照してください。)0xUUは、バイトが持つことができる他の可能な値がコピーによって保持されるのと同じように、コピーによって保持されます。

しかし、詳細はもう少し複雑です。Rustのメモリ内でデータをコピーする方法は2つあります。残念ながら、この詳細はRust言語チームによって明示的に指定されていないため、以下は私の個人的な解釈です。私が言っていることは、他にマークされていない限り、議論の余地はないと思いますが、もちろんそれは間違った印象かもしれません。

型なし/バイト単位のコピー

一般に、バイトの範囲がコピーされている場合、ソースの範囲はターゲットの範囲を上書きするだけなので、ソースの範囲が「0x00 0xUU 0xUU 0xUU」の場合、コピー後、ターゲットの範囲はバイトの正確なリストになります。

これはmemcpy/ memmoveCでの動作と同じです(私の標準の解釈では、残念ながらここではあまり明確ではありません)。Rustでは、ptr::copy{,_nonoverlapping} おそらくバイト単位のコピーを実行しますが、実際には正確に指定されていないため、タイプされているとも言えます。これはこの問題で少し議論されました。

型付きコピー

代わりの方法は「型付きコピー」です。これは、すべての通常の割り当て(=)で、および関数との間で値を渡すときに発生します。型付きコピーは、ソースメモリをあるタイプで解釈し、そのタイプのT値をTターゲットメモリに「再シリアル化」します。

バイト単位のコピーとの主な違いは、型に関係のない情報Tが失われることです。これは基本的に、型指定されたコピーがパディングを「忘れ」、事実上初期化されていない状態にリセットするという複雑な言い方です。型なしコピーと比較して、型付きコピーはより多くの情報を失います。型なしコピーは基になる表現を保持し、型付きコピーは表現された値を保持するだけです。

したがって、に変換0usizeした場合でもPaddingDemo、その値の型付きコピーはこれを "0x00 0xUU 0xUU 0xUU"(またはパディングのための他の可能なバイト)にリセットできます- data保証されていないオフセット0にあると仮定します(#[repr(C)]必要に応じて追加します)その保証)。

あなたの場合、ptr::writeは型の引数を取り、引数はPaddingDemo型付きコピーを介して渡されます。したがって、すでにその時点で、パディングバイトは任意に変更される可能性があり、特に0xUUになる可能性があります。

未初期化 usize

コードにUBがあるかどうかは、さらに別の要因、つまり、初期化されていないバイトがaにusizeあるかどうかに依存します。問題は、(部分的に)初期化されていないメモリの範囲が整数を表しているかどうかです。現在、それはないのでUBがあります。しかし、それが真実であるかどうかはかなり議論されており、最終的にそれを許可する可能性が高いようです。

ただし、他の多くの詳細はまだ不明です。たとえば、「0x00 0xUU 0xUU 0xUU」を整数に変換すると、完全に初期化されていない整数になる可能性があります。つまり、整数は「部分的な初期化」を保持できない場合があります。整数で部分的に初期化されたバイトを保持するには、基本的に整数には抽象的な「値」がなく、単に(おそらく初期化されていない)バイトのシーケンスであると言う必要があります。これは、整数がのような操作でどのように使用されるかを反映していません/。(これの一部は、周りのLLVMの決定に依存poisonしてfreeze、LLVMは整数型で負荷を行うとき、結果が完全であることを決めるかもしれませんpoison任意の入力バイトがある場合poison。)したがって、初期化されていない整数を許可するためにコードがUBでない場合でも、転送するデータが失われているため、期待どおりに動作しない可能性があります。

生のバイトを転送したい場合は、などのそれに適したタイプを使用することをお勧めしますMaybeUninit。整数型を使用する場合、目標は整数値、つまり数値を転送することです。


これは非常に役に立ちます。ありがとうございます。
Lucretiel

したがって、仮に、最後の段落で説明されている動作が形式化された場合(現時点では該当しない)、操作が実行されない限り、usizeはUUバイトを持つことができ、その後変換されます。、私の元の型にこれは、パディングバイトがUUであるかどうかに関係なく機能するためです。
Lucretiel

詳細な回答ありがとうございます!Miriがこの種の未定義の動作を検出することは可能でしょうか?
Sven Marnach

1
@Lucretielは、それusizeがバイトのバッグ(整数ではない)を表すことを決定した場合、そうでusizeあり、MaybeUninit<usize>同等であり、両方とも基礎となるバイトレベル(これには「未定義バイト」を含む)表現を完全に保持します。
Ralf Jung

1
@SvenMarnachの現在の実装はptr::write、初期化されていない末尾のバイトをコピーしないように十分にスマートであるためです。
Lucretiel
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.