Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions src/coding-guidelines/values/gui_uyp3mCj77FS8.rst.inc
Original file line number Diff line number Diff line change
@@ -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: <TODO>
: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
<https://coding-guidelines.arewesafetycriticalyet.org/coding-guidelines/types-and-traits/index.html#gui_UnionPartialInit>`_.

Calling `assume_init <https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.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<T>`` 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<u64>``.
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::<u64>::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<u8>`` can contain uninitialized bytes.
The call to ``std::mem::transmute`` reinterprets the ``[MaybeUninit<u8>; 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<u8>`` 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<u8>; 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::<S>()];
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);
}
1 change: 1 addition & 0 deletions src/coding-guidelines/values/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
Values
======

.. include:: gui_uyp3mCj77FS8.rst.inc