-
Notifications
You must be signed in to change notification settings - Fork 33
#235 [Coding Guideline]: Do not read uninitialized memory of any non-union type as a typed value #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
base: main
Are you sure you want to change the base?
#235 [Coding Guideline]: Do not read uninitialized memory of any non-union type as a typed value #240
Changes from 17 commits
718104f
0d91f92
476b76a
9743036
26eaa0f
68c48f2
aaa83a4
a4c6c83
66b5a4b
e456a33
a95c087
cebdd28
6ce3ddb
26883e6
39fcb24
13c0267
54fda06
5f37374
0524074
6e2cd30
3afff16
43b8103
98863df
ee4a86a
fc521a1
347d9ca
1ef98f2
e9efabf
f84f500
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,274 @@ | ||
| .. 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 of any non-union type 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 :cite:`gui_uyp3mCj77FS8:RUSTNOMICON-UNINIT`. | ||
| 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 `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 :std:`std::mem::MaybeUninit::assume_init` | ||
| :cite:`gui_uyp3mCj77FS8:MAYBEUNINIT-DOC` 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 on memory that is not yet fully initialized is undefined behavior :cite:`gui_uyp3mCj77FS8:RUST-REF-BEHAVIOR`. | ||
rcseacord marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| The memory must be properly initialized according to the requirements of the variable's type :cite:`gui_uyp3mCj77FS8:UCG-VALIDITY`. | ||
| 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 | ||
| :cite:`gui_uyp3mCj77FS8:RUSTNOMICON-UNINIT` :cite:`gui_uyp3mCj77FS8:FERROCENE-SPEC`. | ||
| Reading uninitialized memory as a typed value is undefined behavior :cite:`gui_uyp3mCj77FS8:RUST-REF-BEHAVIOR`. | ||
| This guideline aligns with functional safety standards :cite:`gui_uyp3mCj77FS8:ISO-26262` | ||
| :cite:`gui_uyp3mCj77FS8:IEC-61508` and secure coding practices. | ||
|
|
||
| .. 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:: | ||
| :miri: expect_ub | ||
| :warn: allow | ||
|
|
||
| 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 variable ``x`` of type ``MaybeUninit<u64>``. | ||
rcseacord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| The code calls the ``write`` function to write the value 42 to ``x``. | ||
| The call to ``assume_init`` asserts that the value is initialized and extracts the value of type ``u64``. | ||
| This call is compliant with this rule because the memory has been properly initialized by the call to ``write(42)``. | ||
| This is the canonical safe pattern for using ``MaybeUninit``. | ||
rcseacord marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| .. rust-example:: | ||
| :miri: | ||
|
|
||
| use std::mem::MaybeUninit; | ||
|
|
||
| fn main() { | ||
| let mut x = MaybeUninit::<u64>::uninit(); | ||
| x.write(42); | ||
| // SAFETY: '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. | ||
rcseacord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Not all bit patterns are valid pointers for all operations (e.g., provenance rules) :cite:`gui_uyp3mCj77FS8:UCG-VALIDITY`. | ||
| You cannot create a pointer from unspecified bytes. | ||
rcseacord marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Even a raw pointer type (e.g., ``*const T``) has validity rules :cite:`gui_uyp3mCj77FS8:RUST-REF-BEHAVIOR`. | ||
rcseacord marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| .. rust-example:: | ||
| :miri: expect_ub | ||
| :warn: allow | ||
|
|
||
| use std::mem::MaybeUninit; | ||
|
|
||
| fn main() { | ||
| // Undefined behavior creating a pointer from uninitialized memory | ||
| 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. | ||
rcseacord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Creating a reference from arbitrary or uninitialized bytes is undefined behavior :cite:`gui_uyp3mCj77FS8:RUST-REF-BEHAVIOR`. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this is entirely correct. A reference could be created from arbitrary bytes, e.g. let bytes = 999u32.to_be_bytes();
let nine_nine_nine = u32::from_be_bytes(bytes);
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't find this any longer.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? :O You can't find the sentence that says "Creating a reference from arbitrary or uninitialized bytes is undefined behavior"? It should be at line 119 |
||
| References must be valid, aligned, dereferenceable, and non-null :cite:`gui_uyp3mCj77FS8:UCG-VALIDITY`. | ||
| Uninitialized memory cannot satisfy these requirements. | ||
rcseacord marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| .. rust-example:: | ||
| :miri: expect_ub | ||
| :warn: allow | ||
|
|
||
| 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 has undefined behavior because it creates an invalid reference. | ||
|
|
||
| The ``&u8`` reference has stricter validity requirements than the raw pointer ``*const u8``. | ||
| While ``ptr::dangling()`` produces a non-null, well-aligned pointer, it does not point to valid, allocated memory. | ||
|
|
||
| The code writes a dangling raw pointer into memory and then calls ``assume_init()``, | ||
| asserting that this memory contains a valid reference. | ||
| However, a dangling pointer is never a valid reference—even if you never dereference it. | ||
| The mere existence of an invalid reference is undefined behavior. | ||
|
|
||
| .. rust-example:: | ||
| :miri: expect_ub | ||
|
|
||
| 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:: | ||
| :miri: expect_ub | ||
|
|
||
| use std::mem::MaybeUninit; | ||
|
|
||
| fn main() { | ||
| let 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 two 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:: | ||
| :miri: | ||
|
|
||
| #[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; | ||
| // SAFETY: All fields are initialized (padding doesn't matter) | ||
| let s = unsafe { p.read_unaligned() }; // compliant | ||
| println!("{:?}", s); | ||
| } | ||
| .. bibliography:: | ||
| :id: bib_WNCi5njUWLuY | ||
| :status: draft | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 0 | ||
| :widths: auto | ||
| :class: bibliography-table | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:DO-178C` | ||
| - RTCA, Inc. "DO-178C: Software Considerations in Airborne Systems and Equipment Certification." https://store.accuristech.com/standards/rtca-do-178c?product_id=2200105. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:RUSTNOMICON-UNINIT` | ||
| - The Rust Project Developers. "The Rustonomicon - Uninitialized Memory." https://doc.rust-lang.org/nomicon/uninitialized.html. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:RUST-REF-BEHAVIOR` | ||
| - The Rust Project Developers. "The Rust Reference - Behavior considered undefined." https://doc.rust-lang.org/reference/behavior-considered-undefined.html. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:MAYBEUNINIT-DOC` | ||
| - The Rust Project Developers. "std::mem::MaybeUninit - Rust Standard Library Documentation." https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:FERROCENE-SPEC` | ||
| - Ferrocene GmbH. "Ferrocene Language Specification." https://spec.ferrocene.dev/. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:MISRA-RUST` | ||
| - MISRA Consortium Limited. "MISRA Rust Guidelines (Draft)." https://misra.org.uk/. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:ISO-26262` | ||
| - International Organization for Standardization. "ISO 26262 - Road vehicles - Functional safety." https://www.iso.org/standard/68383.html. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:IEC-61508` | ||
| - International Electrotechnical Commission. "IEC 61508 - Functional Safety of Electrical/Electronic/Programmable Electronic Safety-related Systems." https://www.iec.ch/functional-safety. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:RUST-SAFETY-CRITICAL-WG` | ||
| - Rust Foundation. "Rust Safety-Critical Consortium." https://github.com/rust-lang/safety-critical-consortium. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:UCG-VALIDITY` | ||
| - Rust Unsafe Code Guidelines. "Validity and Safety Invariant." https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#validity-and-safety-invariant. | ||
|
|
||
| * - :bibentry:`gui_uyp3mCj77FS8:CERT-RUST` | ||
| - Carnegie Mellon University Software Engineering Institute. "SEI CERT Rust Coding Standard." https://wiki.sei.cmu.edu/confluence/display/rust | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,3 +6,4 @@ | |
| Values | ||
| ====== | ||
|
|
||
| .. include:: gui_uyp3mCj77FS8.rst.inc | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you say is the difference between this and
std::mem::transmute? Maybe we ought to point out that this is not the same, sincetransmuteis quite the famousunsafefunction.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felix91gr I got this information from @workingjubilee
I believe these things are roughly equivalent in the context they are used here in that they are all typed reads.