diff --git a/src/coding-guidelines/values/gui_uyp3mCj77FS8.rst.inc b/src/coding-guidelines/values/gui_uyp3mCj77FS8.rst.inc new file mode 100644 index 00000000..607e87ae --- /dev/null +++ b/src/coding-guidelines/values/gui_uyp3mCj77FS8.rst.inc @@ -0,0 +1,224 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Do not read uninitialized memory as a typed value + :id: gui_uyp3mCj77FS8 + :category: mandatory + :status: draft + :release: + :fls: fls_6lg0oaaopc26 + :decidability: undecidable + :scope: system + :tags: undefined-behavior, unsafe + + Do not read uninitialized memory of any non-union type as a typed value. + This is sometimes referred to as *transmuting* or *read-at-type*. + Memory can remain uninitialized if it is not read as a type. + + Reading from a union is covered by a separate rule, `Do not read from union fields that may contain uninitialized bytes + `_. + + Calling `assume_init `_ or + any of the following related functions is treated in the same manner as a typed read: + + * ``assume_init_drop`` + * ``assume_init_mut`` + * ``assume_init_read`` + * ``assume_init_ref`` + * ``array_assume_init`` + + Calling any of these function when on memory that is not yet fully initialized is undefined behavior. + The memory must be properly initialized according to the requirements of the variable’s type. + For example, a variable of reference type must be aligned, non-null, and point to valid memory. + Similarly, entirely uninitialized memory may have any content, while a ``bool`` must always be ``true`` or ``false``. + Consequently, reading an uninitialized ``bool`` is undefined behavior. + + .. rationale:: + :id: rat_kjFRrhpS8Wu6 + :status: draft + + Rust's memory model requires that all bytes must be initialized before being read as a typed value. + Reading uninitialized memory as a typed value is undefined behavior. + + .. non_compliant_example:: + :id: non_compl_ex_Qb5GqYTP6db1 + :status: draft + + This noncompliant example extracts a value of type ``u32`` from uninitialized memory within a ``MaybeUninit`` container, + which is undefined behavior. + + .. rust-example:: + + use std::mem::MaybeUninit; + + fn main() { + // Reading uninitialized memory as a typed value is undefined behavior + let x: u32 = unsafe { MaybeUninit::uninit().assume_init() }; // noncompliant + } + + .. compliant_example:: + :id: compl_ex_Ke869nSXuShV + :status: draft + + This compliant example creates an uninitialized allocation of type ``MaybeUninit``. + The code calls the ``write`` function to write the value 42 into the ``MaybeUninit``. + The call to ``assume_init`` asserts that the value is initialized and extracts the value of type ``u64``. + This is valid because the memory has been initialized by the call to ``write(42)``. + This is the canonical safe pattern for using ``MaybeUninit``. + + .. rust-example:: + + use std::mem::MaybeUninit; + + fn main() { + let mut x = MaybeUninit::::uninit(); + x.write(42); + // x is fully initialized + let val = unsafe { x.assume_init() }; // compliant + } + + .. non_compliant_example:: + :id: non_compl_ex_Qb5GqYTP6db4 + :status: draft + + This noncompliant example creates a pointer from uninitialized memory. + Not all bit patterns are valid pointers for all operations (e.g., provenance rules). + You cannot create a pointer from unspecified bytes. + Even a raw pointer type (e.g., ``*const T``) has validity rules. + + .. rust-example:: + + use std::mem::MaybeUninit; + + fn main() { + let p: *const u32 = unsafe { MaybeUninit::uninit().assume_init() }; // noncompliant + } + + .. non_compliant_example:: + :id: non_compl_ex_Qb5GqYTP6db2 + :status: draft + + This noncompliant example creates a reference from uninitialized memory. + Creating a reference from arbitrary or uninitialized bytes is undefined behavior. + References must be valid, aligned, dereferenceable, and non-null. + Uninitialized memory cannot satisfy these invariants. + + .. rust-example:: + + use std::mem::MaybeUninit; + + fn main() { + // Reading an invalid reference is undefined behavior + let r: &u32 = unsafe { MaybeUninit::uninit().assume_init() }; // noncompliant + } + + .. non_compliant_example:: + :id: non_compl_ex_Qb5GqYTP6db3 + :status: draft + + This noncompliant example creates a reference from uninitialized memory. + The ``create_ref`` function has undefined behavior when creating a reference from a dangling pointer. + It creates uninitialized memory sized for a reference (``&u8``). + A reference is essentially a pointer (8 bytes on 64-bit systems). + ``&raw mut uninit`` retrieves a raw mutable pointer to the ``MaybeUninit``. + The ``.cast::<*const u8>()`` reinterprets it as a pointer to a raw pointer. + The call to ``.write(ptr::dangling())`` writes a dangling pointer value. + This creates a pointer which is non-null, aligned, but does not point to valid, initialized value. + The call to ``assume_init`` asserts the ``MaybeUninit<&u8>`` is a valid ``&u8`` reference + A reference (``&T``) has stricter requirements than a raw pointer. + Even though the bit pattern looks like a valid pointer, + the semantic requirements for a reference are violated. + The compiler is allowed to assume references always point to valid data, + so this can cause miscompilation. + + .. rust-example:: + + use std::mem::MaybeUninit; + use std::ptr; + + fn create_ref() { + let mut uninit: MaybeUninit<&u8> = MaybeUninit::uninit(); + unsafe { + // write non-null and aligned address. + (&raw mut uninit).cast::<*const u8>().write(ptr::dangling()); + // Undefined behavior occurs when asserting 'uninit' is a valid reference. + let _init = uninit.assume_init(); // noncompliant + } + } + + fn main() { + create_ref(); + } + + .. non_compliant_example:: + :id: non_compl_ex_Qb5GqYTP6db5 + :status: draft + + Array elements must individually be valid values. + This noncompliant example creates an uninitialized array of four ``u8`` values. + The call to ``.assume_init`` asserting that the array is initialized is valid here because + an array of ``MaybeUninit`` can contain uninitialized bytes. + The call to ``std::mem::transmute`` reinterprets the ``[MaybeUninit; 4]`` as ``[u8; 4]``. + This is undefined behavior, because the bytes were never initialized. + Even though all bit patterns (0-255) are valid for the ``u8`` type, the values must be initialized. + + ``MaybeUninit`` can hold uninitialized memory — that's its purpose. + ``u8`` cannot hold uninitialized memory — all 8 bits must be defined. + The ``transmute`` performs a typed read that asserts the bytes are valid ``u8`` values. + Reading uninitialized bytes as a concrete type is always undefined behavior. + + .. rust-example:: + + use std::mem::MaybeUninit; + + fn main() { + let mut arr: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; + // Undefined behavior constructing an array of 'u8' from uninitialized memory. + let a = unsafe { std::mem::transmute::<_, [u8; 4]>(arr) }; // noncompliant + } + + .. compliant_example:: + :id: compl_ex_Ke869nSXuShW + :status: draft + + This compliant example defines a C-layout ``struct`` with: + + * ``a``: 1 byte at offset 0 + * 3 bytes of padding (to align ``b`` to 4 bytes) + * ``b``: 4 bytes at offset 4 + * Total size: 8 bytes + + The variable ``buf`` is a fully, zero-initialized 8-byte buffer. + + The first wo bytes of ``buf`` are overwritten. + The byte buffer ``buf`` pointer is cast to a pointer to ``S``. + The call to ``read_unaligned`` reads the ``struct`` without requiring alignment. + + This example is compliant because: + + * All bytes are initialized (buffer was zero-initialized) + * All fields have valid values (``u8`` and ``u32`` accept any bit pattern) + * Padding bytes don't need to be any specific value + * ``read_unaligned`` handles the alignment issue + + .. rust-example:: + + #[repr(C)] + #[derive(Debug)] + struct S { + a: u8, + b: u32, + } + + fn main() { + let mut buf = [0u8; std::mem::size_of::()]; + buf[0] = 10; + buf[1] = 20; // writing padding is fine + + let p = buf.as_ptr() as *const S; + // All fields are initialized (padding doesn't matter) + let s = unsafe { p.read_unaligned() }; // compliant + println!("{:?}", s); + } diff --git a/src/coding-guidelines/values/index.rst b/src/coding-guidelines/values/index.rst index c2419fc7..16eb6636 100644 --- a/src/coding-guidelines/values/index.rst +++ b/src/coding-guidelines/values/index.rst @@ -6,3 +6,4 @@ Values ====== +.. include:: gui_uyp3mCj77FS8.rst.inc