-
Notifications
You must be signed in to change notification settings - Fork 29
#235 [Coding Guideline]: Do not create values from uninitialized memory #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
manhatsu
wants to merge
12
commits into
rustfoundation:main
Choose a base branch
from
manhatsu:doc/no-uninit-value
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7713f0d
feat: split into own file
PLeVasseur 2ade1b3
feat: remove description related to union
rcseacord 8e147c4
Update gui_uyp3mCj77FS8.rst.inc
rcseacord 7b27536
Update gui_uyp3mCj77FS8.rst.inc
rcseacord 807d57c
Update gui_uyp3mCj77FS8.rst.inc
rcseacord 4e17069
Update gui_uyp3mCj77FS8.rst.inc
rcseacord db75b00
Update gui_uyp3mCj77FS8.rst.inc
rcseacord 39ff5a0
Update gui_uyp3mCj77FS8.rst.inc
rcseacord 50101d2
Update src/coding-guidelines/values/gui_uyp3mCj77FS8.rst.inc
rcseacord 103fbba
Update guideline on reading uninitialized memory
rcseacord 9fe63cb
Update citations in gui_uyp3mCj77FS8.rst.inc
rcseacord 802b444
Remove unnecessary newline in gui_uyp3mCj77FS8.rst.inc
rcseacord File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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`` retreives 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 asseting '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 initalized. | ||
|
|
||
| ``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); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,3 +6,4 @@ | |
| Values | ||
| ====== | ||
|
|
||
| .. include:: gui_uyp3mCj77FS8.rst.inc | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.