-
Notifications
You must be signed in to change notification settings - Fork 39
[Coding Rule]: assure visibility of unsafe keyword in unsafe code #358
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
Changes from all commits
cc225e1
94ef378
e860707
afa1cf4
7a9106b
eb5bdad
a6703e8
e1044d5
fe6c44f
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,299 @@ | ||
| .. SPDX-License-Identifier: MIT OR Apache-2.0 | ||
| SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors | ||
|
|
||
| .. default-domain:: coding-guidelines | ||
|
|
||
| Assure visibility of ``unsafe`` keyword in unsafe code | ||
| ====================================================== | ||
|
|
||
| .. guideline:: Assure visibility of ``unsafe`` keyword in unsafe code | ||
| :id: gui_ZDLZzjeOwLSU | ||
| :category: required | ||
| :status: draft | ||
| :release: 1.85-latest | ||
| :fls: fls_8kqo952gjhaf | ||
| :decidability: decidable | ||
| :scope: crate | ||
| :tags: readability, reduce-human-error | ||
|
|
||
| Mark all code that may violate safety guarantees with a visible ``unsafe`` keyword | ||
| :cite:`gui_ZDLZzjeOwLSU:RUST-REF-UNSAFE-KEYWORD`. | ||
|
|
||
| The following constructs require explicit ``unsafe`` visibility: | ||
|
|
||
| * ``extern`` blocks must be declared as ``unsafe extern`` | ||
| * The ``#[no_mangle]`` attribute must be written as ``#[unsafe(no_mangle)]`` | ||
| * The ``#[export_name]`` attribute must be written as ``#[unsafe(export_name)]`` | ||
| * The ``#[link_section]`` attribute must be written as ``#[unsafe(link_section)]`` | ||
|
|
||
| .. note:: | ||
|
|
||
| Starting with Rust Edition 2024, the use of ``unsafe`` is required in these contexts. | ||
| The ``#[link]`` and ``#[link_ordinal]`` attributes are implicitly covered by the | ||
| ``unsafe extern`` requirement, as they must appear on ``extern`` blocks. | ||
| See rust-lang/rust#82499 for the tracking issue on unsafe attributes. | ||
|
|
||
| .. rationale:: | ||
| :id: rat_eQV3s9ggNegr | ||
| :status: draft | ||
|
|
||
| * Auditability and review | ||
|
|
||
| * ``unsafe`` blocks create clear audit boundaries where reviewers can focus on code | ||
| that may violate Rust's safety guarantees :cite:`gui_ZDLZzjeOwLSU:RUSTNOMICON-MEET-SAFE` | ||
| * Safety-critical standards like ISO 26262 :cite:`gui_ZDLZzjeOwLSU:ISO-26262` and | ||
| DO-178C :cite:`gui_ZDLZzjeOwLSU:DO-178C` require traceability of hazardous operations | ||
| * Helps enumerate and document all places where safety requirements must be manually upheld | ||
| * Satisfies ISO 26262 Part 6, Table 1, objective 1c (use of language subsets) | ||
| * Satisfies DO-178C Section 6.3.4.f (source code traceability) | ||
|
|
||
| * Explicit acknowledgment of responsibility | ||
|
|
||
| * The ``unsafe`` keyword signals that the programmer is taking responsibility for | ||
| upholding invariants the compiler cannot verify | ||
| * Prevents accidental use of ``unsafe`` operations without conscious decision | ||
| * Aligns with the principle of defense in depth in safety-critical systems | ||
|
|
||
| * Static analysis and tooling | ||
|
|
||
| * Tools like ``cargo-geiger`` :cite:`gui_ZDLZzjeOwLSU:CARGO-GEIGER`, ``unsafe-inspect``, | ||
| and custom linters can automatically locate and count unsafe blocks | ||
| * Enables metrics like "unsafe density" for safety assessments | ||
| * Supports qualification evidence required by certification standards | ||
|
|
||
| * Traceability for certification | ||
|
|
||
| * Safety-critical certifications require demonstrating that hazardous operations are | ||
| identified and controlled | ||
| * Visible ``unsafe`` tokens provide direct linkage to safety cases and hazard analyses | ||
| * Facilitates the required documentation that each unsafe operation has been reviewed | ||
| and justified | ||
|
|
||
| .. non_compliant_example:: | ||
| :id: non_compl_ex_FdmuPXGZr4EP | ||
| :status: draft | ||
|
|
||
| The ``#[no_mangle]`` attribute is unsafe because it can be used to declare a function | ||
| identifier that conflicts with an existing symbol. This noncompliant example declares an | ||
| unmangled function named ``convert`` that is missing the required unsafe wrapper in Rust 2024. | ||
|
|
||
| This noncompliant example requires Rust Edition 2021 or earlier to compile. | ||
| In Rust Edition 2024, missing ``unsafe`` wrappers cause compilation errors. | ||
|
|
||
| .. rust-example:: | ||
| :compile_fail: | ||
| :edition: 2024 | ||
|
|
||
| // Undefined behavior by the linker or loader is possible | ||
| // if another 'convert' function is defined. | ||
| #[no_mangle] | ||
| fn convert() {} | ||
|
|
||
| fn main() { | ||
| convert(); | ||
| } | ||
|
|
||
| .. compliant_example:: | ||
| :id: compl_ex_wR1FEyLRKmrr | ||
| :status: draft | ||
|
|
||
| Rust Edition 2024 enforces that the ``no_mangle`` attribute requires an ``unsafe`` keyword, | ||
| as shown in this compliant example. | ||
|
|
||
| NOTE: This code can still have undefined behavior if the ``convert`` function symbol is | ||
| defined more than once. | ||
|
|
||
| .. rust-example:: | ||
| :miri: skip | ||
| :edition: 2024 | ||
|
|
||
| #[unsafe(no_mangle)] // compliant | ||
| fn convert() {} | ||
|
|
||
| fn main() { | ||
| convert(); | ||
| } | ||
|
|
||
| .. non_compliant_example:: | ||
| :id: non_compl_ex_FdmuPXGZr4EO | ||
| :status: draft | ||
|
|
||
| This noncompliant example misdeclares the ``malloc`` function in an ``extern "C"`` block | ||
| by specifying the type of the ``size`` parameter as ``f32`` instead of ``usize``. | ||
|
|
||
| An ``extern`` block is unsafe because undefined behavior can occur if types or functions are | ||
| misdeclared. This is true even if the declarations are not used. This noncompliant example | ||
| requires Rust Edition 2021 or earlier to compile. | ||
|
|
||
| .. rust-example:: | ||
| :compile_fail: | ||
| :edition: 2024 | ||
|
|
||
| use std::ffi; | ||
|
|
||
| extern "C" { | ||
| // If 'malloc' is otherwise defined with a 'usize' argument, the compiler | ||
| // may generate code for calls to this function using this incompatible declaration, | ||
| // resulting in undefined behavior. | ||
| fn malloc(size: f32) -> *mut ffi::c_void; | ||
| } | ||
|
|
||
| fn main() {} | ||
|
|
||
| .. compliant_example:: | ||
| :id: compl_ex_wR1FEyLRKmrq | ||
| :status: draft | ||
|
|
||
| Rust Edition 2024 enforces that ``extern "C"`` blocks require an ``unsafe`` keyword, | ||
| as shown in this compliant example. | ||
|
|
||
| NOTE: This code can still have undefined behavior if the declared ``malloc`` function is | ||
| incompatible with the actual definition. To eliminate this undefined behavior, the | ||
| declaration for ``malloc`` used in this compliant example accepts one argument of type ``usize``. | ||
|
|
||
| .. rust-example:: | ||
| :miri: skip | ||
| :edition: 2024 | ||
|
|
||
| use std::ffi; | ||
|
|
||
| unsafe extern "C" { | ||
| // Here the assumption is that malloc is the one defined by C's stdlib.h | ||
| // and that size_of::<usize>() == size_of::<size_t>() | ||
|
Contributor
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. May be a bit picky, but this should probably be asserted on, as in |
||
| fn malloc(size: usize) -> *mut ffi::c_void; | ||
| fn free(ptr: *mut ffi::c_void); | ||
| } | ||
|
|
||
| fn main() { | ||
| unsafe { | ||
| let ptr = malloc(1024); | ||
| if !ptr.is_null() { | ||
| free(ptr); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| .. non_compliant_example:: | ||
| :id: non_compl_ex_Hk3mNp5qRs7t | ||
| :status: draft | ||
|
|
||
| The ``#[export_name]`` and ``#[link_section]`` attributes can cause undefined behavior if | ||
| misused, as they affect symbol resolution and memory layout at link time. Without the | ||
| ``unsafe`` keyword, these hazards are not visible to reviewers or tools. | ||
|
|
||
| This noncompliant example has two separate problems. First, it uses an ``#[export_name]`` | ||
| attribute without an unsafe wrapper. This attribute controls the symbol name used during | ||
| linking. If another symbol with the same name exists, it causes undefined behavior. | ||
| Rust 2024 requires it to be marked ``unsafe``. | ||
|
|
||
| The second problem is that this noncompliant example uses a ``#[link_section]`` attribute | ||
| without an unsafe wrapper. This attribute places the item in a specific linker section. | ||
| Incorrect section placement can cause undefined behavior (e.g., placing mutable data in | ||
| read-only sections, or interfering with special sections like ``.init``). | ||
|
|
||
| This noncompliant example requires Rust Edition 2021 or earlier to compile. | ||
|
|
||
| .. rust-example:: | ||
| :compile_fail: | ||
| :edition: 2024 | ||
|
|
||
| // Collides with the C library 'printf' function | ||
| #[export_name = "printf"] // noncompliant | ||
|
|
||
| // Missing unsafe marker - noncompliant in Rust 2024 | ||
| #[link_section = ".init_array"] // noncompliant | ||
| static DATA: u32 = 42; // Corrupts initialization table! | ||
|
|
||
| fn main() { | ||
| println!("DATA = {DATA}"); | ||
| } | ||
|
|
||
| .. compliant_example:: | ||
| :id: compl_ex_xY2zAb3cDe4f | ||
| :status: draft | ||
|
|
||
| The ``#[export_name]`` and ``#[link_section]`` attributes must use the ``unsafe()`` wrapper | ||
| to make their safety implications visible. | ||
|
|
||
| .. rust-example:: | ||
| :miri: skip | ||
| :edition: 2024 | ||
|
|
||
| // SAFETY: 'custom_symbol' does not conflict with any other symbol | ||
| #[unsafe(export_name = "custom_symbol")] | ||
| pub fn my_function() {} | ||
|
|
||
| // SAFETY: Placing data in a specific section for embedded systems | ||
| #[unsafe(link_section = ".noinit")] | ||
| static mut PERSISTENT_DATA: [u8; 256] = [0; 256]; | ||
|
|
||
| // SAFETY: Custom section for shared memory | ||
| #[unsafe(link_section = ".shared")] | ||
| static SHARED_BUFFER: [u8; 4096] = [0; 4096]; | ||
|
|
||
| fn main() { | ||
| my_function(); | ||
| println!("shared buffer size = {}", SHARED_BUFFER.len()); | ||
| unsafe { let _ = PERSISTENT_DATA[0]; } | ||
| } | ||
|
|
||
| **Enforcement** | ||
| This guideline can be enforced through the following mechanisms: | ||
|
|
||
| * **Rust Edition 2024**: Migrating to Rust Edition 2024 makes violations of this guideline | ||
| compilation errors for ``extern`` blocks and unsafe attributes. | ||
|
|
||
| * **Compiler Lints**: Enable the following lints: | ||
|
|
||
| * ``#![deny(unsafe_code)]`` - Denies all unsafe code (use ``#[allow(unsafe_code)]`` for justified exceptions) | ||
| * ``#![deny(unsafe_op_in_unsafe_fn)]`` - Requires explicit unsafe blocks within unsafe functions | ||
| * ``#![warn(unsafe_attr_outside_unsafe)]`` - Warns about unsafe attributes without the ``unsafe()`` wrapper (pre-2024) | ||
|
|
||
| * **Static Analysis Tools**: | ||
|
|
||
| * ``cargo-geiger`` - Counts and reports unsafe code usage | ||
| * ``cargo-audit`` - Checks for known vulnerabilities in dependencies | ||
| * Custom Clippy lints for project-specific requirements | ||
|
Comment on lines
+252
to
+256
Contributor
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. While this part is informative, it's not helping enforcing the rule. I think this should be moved from this section. |
||
|
|
||
| * **Code Review**: Manual review of all code containing ``unsafe`` tokens should be | ||
| part of the development process, with documented justification for each usage. | ||
|
|
||
| **Related guidelines** | ||
| * Minimize the scope of unsafe blocks | ||
| * Document safety invariants for all unsafe code with ``// SAFETY:`` comments | ||
| * Prefer safe abstractions over raw unsafe code | ||
| * Use ``#![forbid(unsafe_code)]`` at crate level where possible, with explicit exceptions | ||
|
|
||
| .. bibliography:: | ||
| :id: bib_n8YJHvQf4mWx | ||
| :status: draft | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 0 | ||
| :widths: auto | ||
| :class: bibliography-table | ||
|
|
||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-EDITION-GUIDE` | ||
| - The Rust Edition Guide. "Rust 2024." https://doc.rust-lang.org/edition-guide/rust-2024/index.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-REF-UNSAFE-KEYWORD` | ||
| - The Rust Reference. "Unsafe Keyword." https://doc.rust-lang.org/reference/unsafe-keyword.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-LINT-UNSAFE` | ||
| - Rust Compiler Lint Documentation. "unsafe_code." https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-code. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUSTNOMICON-MEET-SAFE` | ||
| - The Rustonomicon. "Meet Safe and Unsafe." https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:ISO-26262` | ||
| - International Organization for Standardization. "ISO 26262 - Road vehicles - Functional safety." https://www.iso.org/standard/68383.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:DO-178C` | ||
| - RTCA, Inc. "DO-178C: Software Considerations in Airborne Systems and Equipment Certification." https://store.accuristech.com/standards/rtca-do-178c. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:CARGO-GEIGER` | ||
| - cargo-geiger contributors. "cargo-geiger: Detects usage of unsafe Rust." https://github.com/geiger-rs/cargo-geiger. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-REF-EXTERN` | ||
| - The Rust Reference. "External blocks." https://doc.rust-lang.org/reference/items/external-blocks.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-REF-UNSAFE-ATTR` | ||
| - The Rust Reference. "Unsafe attributes." https://doc.rust-lang.org/reference/attributes.html#unsafe-attributes. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:FERROCENE-SPEC` | ||
| - Ferrocene GmbH. "Ferrocene Language Specification." https://spec.ferrocene.dev/. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:RUST-REF-UNION` | ||
| - The Rust Reference. "Unions." https://doc.rust-lang.org/reference/items/unions.html. | ||
| * - :bibentry:`gui_ZDLZzjeOwLSU:UCG-VALIDITY` | ||
| - Rust Unsafe Code Guidelines. "Validity and Safety Invariant." https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#validity-and-safety-invariant. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,3 +5,10 @@ | |
|
|
||
| Attributes | ||
| ========== | ||
|
|
||
| .. toctree:: | ||
| :maxdepth: 1 | ||
| :titlesonly: | ||
| :glob: | ||
|
|
||
| gui_* | ||
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.
Afaiu this rule is only meaningful for edition <2024. If this is correct, should we add this to the metadata in this section? Would we need a new metdata field for this?