From 686c7a5f5a14f89153879f2403e5515413c0e5b4 Mon Sep 17 00:00:00 2001 From: hg-anssi Date: Tue, 22 Jul 2025 11:07:39 +0200 Subject: [PATCH 1/4] refacto --- src/en/04_language.md | 209 ------------------------ src/en/04_standard.md | 234 ++++++++++++++++++++++++++ src/en/05_central_traits.md | 59 +++++++ src/en/05_errors.md | 86 ++++++++++ src/en/05_guarantees.md | 33 ++++ src/en/05_integer.md | 48 ++++++ src/en/05_macros.md | 8 + src/en/05_naming.md | 30 ++++ src/en/05_typesystem.md | 292 --------------------------------- src/en/SUMMARY.md | 21 ++- src/fr/04_language.md | 225 ------------------------- src/fr/04_standard.md | 252 ++++++++++++++++++++++++++++ src/fr/05_central_traits.md | 66 ++++++++ src/fr/05_errors.md | 101 ++++++++++++ src/fr/05_guarantees.md | 39 +++++ src/fr/05_integer.md | 50 ++++++ src/fr/05_macros.md | 8 + src/fr/05_naming.md | 32 ++++ src/fr/05_typesystem.md | 316 ------------------------------------ src/fr/SUMMARY.md | 21 ++- 20 files changed, 1082 insertions(+), 1048 deletions(-) delete mode 100644 src/en/04_language.md create mode 100644 src/en/04_standard.md create mode 100644 src/en/05_central_traits.md create mode 100644 src/en/05_errors.md create mode 100644 src/en/05_guarantees.md create mode 100644 src/en/05_integer.md create mode 100644 src/en/05_macros.md create mode 100644 src/en/05_naming.md delete mode 100644 src/fr/04_language.md create mode 100644 src/fr/04_standard.md create mode 100644 src/fr/05_central_traits.md create mode 100644 src/fr/05_errors.md create mode 100644 src/fr/05_guarantees.md create mode 100644 src/fr/05_integer.md create mode 100644 src/fr/05_macros.md create mode 100644 src/fr/05_naming.md diff --git a/src/en/04_language.md b/src/en/04_language.md deleted file mode 100644 index f51d715f..00000000 --- a/src/en/04_language.md +++ /dev/null @@ -1,209 +0,0 @@ -# Language generalities - -## Language guarantees - -### Undefined Behaviors (*UB*) - -> The behavior of a program is *undefined* when its semantics is not described in the Rust language. - -The existence of UB is considered an [error](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.general). - -For example, dereferencing the null pointer is a *UB*. On the other hand, `unwrap`ing the `None` object is well defined because it is the language that processes this error (by launching a panic). - -The current list of *UBs* is given in the language [reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). Notice the following guarantees: - -* No dereference of pointer to an unallocated or unaligned memory address (dangling pointer), which implies - * No buffer overflow - * No access to freed memory - * No non-aligned access -* The pointed values are [consistent](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.invalid) with the pointer's type. For example, a value pointed at by a boolean pointer will be byte of value 1 or 0. -* Respect of [aliasing rules](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.alias) (see also [nomicon](https://doc.rust-lang.org/nomicon/aliasing.html)): a mutable reference cannot be shared. -* No concurrent access (reading/writing is not possible while writing) to the same memory address ([data race](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.race), see also [nomicon](https://doc.rust-lang.org/nomicon/races.html)) - -### Rust guarantees - -> The language paradigm is to ensure the absence of a UB in a program using only the non-*unsafe* part of Rust. - -However, the language does not prevent - -* resource leaks (memory, IO, ...), -* numeric overflows. - -### References - -* https://doc.rust-lang.org/reference/unsafety.html -* https://doc.rust-lang.org/nomicon/what-unsafe-does.html - -## Naming - -As of now, the standard library is the de facto standard for naming things in -the Rust world. However, an effort has been made to formalize it, first in -[RFC 430], then in the [Rust API Guidelines]. - -The basic rule consists in using : - -- `UpperCamelCase` for types, traits, enum variants, -- `snake_case` for functions, methods, macros, variables and modules, -- `SCREAMING_SNAKE_CASE` for statics and constants, -- `'lowercase` for lifetimes. - -The [Rust API Guidelines] also prescribes more precise naming conventions for -some particular constructions: - -- (C-CONV) for conversion methods (`as_`, `to_`, `into_`), -- (C-GETTER) for getters, -- (C-ITER) for iterator-producing methods, -- (C-ITER-TY) for iterator types, -- (C-FEATURE) for feature naming, -- (C-WORD-ORDER) for word order consistency. - -> **Rule {{#check LANG-NAMING | Respect naming conventions}}** -> -> Development of a secure application must follow the naming conventions -> outlined in the [Rust API Guidelines]. - -[rfc 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md -[rust api guidelines]: https://rust-lang.github.io/api-guidelines/ - -## Integer overflows - -Although some verification is performed by Rust regarding potential integer -overflows, precautions should be taken when executing arithmetic operations on -integers. - -In particular, it should be noted that using debug or release compilation -profile changes integer overflow behavior. In debug configuration, overflow -cause the termination of the program (`panic`), whereas in the release -configuration the computed value silently wraps around the maximum value that -can be stored. - -This last behavior can be made explicit by using the `Wrapping` generic type, -or the `overflowing_` and `wrapping_` operations on integers -(the `` part being `add`, `mul`, `sub`, `shr`, etc.). - -```rust -use std::num::Wrapping; -# use std::panic; - -# fn main() { -let x: u8 = 242; - -# let result = panic::catch_unwind(|| { -println!("{}", x + 50); // panics in debug, prints 36 in release. -# }); -# if result.is_err() { println!("panic"); } -println!("{}", x.overflowing_add(50).0); // always prints 36. -println!("{}", x.wrapping_add(50)); // always prints 36. -println!("{}", Wrapping(x) + Wrapping(50)); // always prints 36. - -// always panics: -let (res, c) = x.overflowing_add(50); -# let result = panic::catch_unwind(|| { -if c { panic!("custom error"); } -else { println!("{}", res); } -# }); -# if result.is_err() { println!("panic"); } -# } -``` - -> **Rule {{#check LANG-ARITH | Use appropriate arithmetic operations regarding potential overflows}}** -> -> When assuming that an arithmetic operation can produce an overflow, the -> specialized functions `overflowing_`, `wrapping_`, or the -> `Wrapping` type must be used. - - - -## Error handling - - - -The `Result` type is the preferred way of handling functions that can fail. -A `Result` object must be tested, and never ignored. - -> **Recommendation {{#check LANG-ERRWRAP | Implement custom `Error` type, wrapping all possible errors}}** -> -> A crate can implement its own `Error` type, wrapping all possible errors. -> It must be careful to make this type exception-safe (RFC 1236), and implement -> `Error + Send + Sync + 'static` as well as `Display`. - -> **Recommendation {{#check LANG-ERRDO | Use the `?` operator and do not use the `try!` macro}}** -> -> The `?` operator should be used to improve readability of code. -> The `try!` macro should not be used. - -Third-party crates may be used to facilitate error handling. Most of them -(notably [failure], [snafu], [thiserror]) address the creation of new custom -error types that implement the necessary traits and allow wrapping other -errors. - -Another approach (notably proposed in the [anyhow] crate) consists in an automatic -wrapping of errors into a single universal error type. Such wrappers should not -be used in libraries and complex systems because they do not allow developers to -provide context to the wrapped error. - -[failure]: https://crates.io/crates/failure -[snafu]: https://crates.io/crates/snafu -[thiserror]: https://crates.io/crates/thiserror -[anyhow]: https://crates.io/crates/anyhow - -### Panics - -Explicit error handling (`Result`) should always be preferred instead of calling -`panic`. The cause of the error should be available, and generic errors should -be avoided. - -Crates providing libraries should never use functions or instructions that can -fail and cause the code to panic. - -Common patterns that can cause panics are: - -- using `unwrap` or `expect`, -- using `assert`, -- an unchecked access to an array, -- integer overflow (in debug mode), -- division by zero, -- large allocations, -- string formatting using `format!`. - -> **Rule {{#check LANG-NOPANIC | Don't use functions that can cause `panic!`}}** -> -> Functions or instructions that can cause the code to panic at runtime must not -> be used. - -> **Rule {{#check LANG-ARRINDEXING | Test properly array indexing or use the `get` method}}** -> -> Array indexing must be properly tested, or the `get` method should be used to -> return an `Option`. - - - - -### FFI and panics - -When calling Rust code from another language (for ex. C), the Rust code must -be careful to never panic. -Stack unwinding from Rust code into foreign code results in undefined behavior. - -> **Rule {{#check LANG-FFIPANIC | Handle correctly `panic!` in FFI}}** -> -> Rust code called from FFI must either ensure the function cannot panic, or use -> `catch_unwind` or the `std::panic` module to ensure the rust code will not -> abort or return in an unstable state. - -Note that `catch_unwind` will only catch unwinding panics, not those that abort -the process. - - - - diff --git a/src/en/04_standard.md b/src/en/04_standard.md new file mode 100644 index 00000000..e2f23cab --- /dev/null +++ b/src/en/04_standard.md @@ -0,0 +1,234 @@ +# Standard library + +## `Send` and `Sync` traits + +The `Send` and `Sync` traits (defined in `std::marker` or `core::marker`) are +marker traits used to ensure the safety of concurrency in Rust. When implemented +correctly, they allow the Rust compiler to guarantee the absence of data races. +Their semantics is as follows: + +- A type is `Send` if it is safe to send (move) it to another thread. +- A type is `Sync` if it is safe to share a immutable reference to it with + another thread. + +Both traits are _unsafe traits_, i.e., the Rust compiler does not verify in any +way that they are implemented correctly. The danger is real: an incorrect +implementation may lead to **undefined behavior**. + +Fortunately, in most cases, one does not need to implement it. In Rust, +almost all primitive types are `Send` and `Sync`, and for most compound types +the implementation is automatically provided by the Rust compiler. +Notable exceptions are: + +- Raw pointers are neither `Send` nor `Sync` because they offer no safety + guards. +- `UnsafeCell` is not `Sync` (and as a result `Cell` and `RefCell` aren't + either) because they offer interior mutability (mutably shared value). +- `Rc` is neither `Send` nor `Sync` because the reference counter is shared and + unsynchronized. + +Automatic implementation of `Send` (resp. `Sync`) occurs for a compound type +(structure or enumeration) when all fields have `Send` types (resp. `Sync` +types). Using an unstable feature (as of Rust 1.37.0), one can block the +automatic implementation of those traits with a manual +_negative implementation_: + +```rust,ignore,noplaypen +#![feature(option_builtin_traits)] + +struct SpecialType(u8); +impl !Send for SpecialType {} +impl !Sync for SpecialType {} +``` + +The negative implementation of `Send` or `Sync` are also used in the standard +library for the exceptions, and are automatically implemented when appropriate. +As a result, the generated documentation is always explicit: a type implements +either `Send` or `!Send` (resp. `Sync` or `!Sync`). + +As a stable alternative to negative implementation, one can use a `PhantomData` +field: + +```rust,noplaypen +# use std::marker::PhantomData; +# +struct SpecialType(u8, PhantomData<*const ()>); +``` + +> **Recommendation {{#check LANG-SYNC-TRAITS | Justify `Send` and `Sync` implementation}}** +> +> In a Rust secure development, the manual implementation of the `Send` and +> `Sync` traits should be avoided and, if necessary, should be justified, +> documented and peer-reviewed. + +## Comparison traits (`PartialEq`, `Eq`, `PartialOrd`, `Ord`) + +Comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) in Rust relies on four standard +traits available in `std::cmp` (or `core::cmp` for `no_std` compilation): + +- `PartialEq` that defines a partial equivalence between + objects of types `Self` and `Rhs`, +- `PartialOrd` that defines a partial order between objects of types + `Self` and `Rhs`, +- `Eq` that defines a total equivalence between objects of the same + type. It is only a marker trait that requires `PartialEq`! +- `Ord` that defines the total order between objects of the same type. + It requires that `PartialOrd` is implemented. + +As documented in the standard library, Rust assumes **a lot of invariants** +about the implementations of those traits: + +- For `PartialEq` + + - *Internal consistency*: `a.ne(b)` is equivalent to `!a.eq(b)`, i.e., `ne` is + the strict inverse of `eq`. The default implementation of `ne` is precisely + that. + + - *Symmetry*: `a.eq(b)` and `b.eq(a)`, are equivalent. From the developer's + point of view, it means: + + - `PartialEq` is implemented for type `A` (noted `A: PartialEq`), + - `PartialEq` is implemented for type `B` (noted `B: PartialEq`), + - both implementations are consistent with each other. + + - *Transitivity*: `a.eq(b)` and `b.eq(c)` implies `a.eq(c)`. It means that: + + - `A: PartialEq`, + - `B: PartialEq`, + - `A: PartialEq`, + - the three implementations are consistent with each other (and their + symmetric implementations). + +- For `Eq` + + - `PartialEq` is implemented. + + - *Reflexivity*: `a.eq(a)`. This stands for `PartialEq` (`Eq` does not + provide any method). + +- For `PartialOrd` + + - *Equality consistency*: + `a.eq(b)` is equivalent to `a.partial_cmp(b) == Some(std::ordering::Eq)`. + + - *Internal consistency*: + + - `a.lt(b)` iff `a.partial_cmp(b) == Some(std::ordering::Less)`, + - `a.gt(b)` iff `a.partial_cmp(b) == Some(std::ordering::Greater)`, + - `a.le(b)` iff `a.lt(b) || a.eq(b)`, + - `a.ge(b)` iff `a.gt(b) || a.eq(b)`. + + Note that by only defining `partial_cmp`, the internal consistency is + guaranteed by the default implementation of `lt`, `le`, `gt`, and `ge`. + + - *Antisymmetry*: `a.lt(b)` (respectively `a.gt(b)`) implies `b.gt(a)` + (respectively, `b.lt(b)`). From the developer's standpoint, it also means: + + - `A: PartialOrd`, + - `B: PartialOrd`, + - both implementations are consistent with each other. + + - *Transitivity*: `a.lt(b)` and `b.lt(c)` implies `a.lt(c)` (also with `gt`, + `le` and `ge`). It also means: + + - `A: PartialOrd`, + - `B: PartialOrd`, + - `A: PartialOrd`, + - the implementations are consistent with each other (and their symmetric). + +- For `Ord` + + - `PartialOrd` + + - *Totality*: `a.partial_cmp(b) != None` always. In other words, + exactly one of `a.eq(b)`, `a.lt(b)`, `a.gt(b)` is true. + + - *Consistency with `PartialOrd`*: `Some(a.cmp(b)) == a.partial_cmp(b)`. + +The compiler do not check any of those requirements except for the type checking +itself. However, comparisons are critical because they intervene both in +liveness critical systems such as schedulers and load balancers, and in +optimized algorithms that may use `unsafe` blocks. +In the first use, a bad ordering may lead to availability issues such as +deadlocks. +In the second use, it may lead to classical security issues linked to memory +safety violations. That is again a factor in the practice of limiting the use +of `unsafe` blocks. + +> **Rule {{#check LANG-CMP-INV | Respect the invariants of standard comparison traits}}** +> +> In a Rust secure development, the implementation of standard comparison traits +> must respect the invariants described in the documentation. + +> **Recommendation {{#check LANG-CMP-DEFAULTS | Use the default method implementation of standard comparison traits}}** +> +> In a Rust secure development, the implementation of standard comparison traits +> should only define methods with no default implementation, so as to reduce +> the risk of violating the invariants associated with the traits. + +There is a Clippy lint to check that `PartialEq::ne` is not defined in +`PartialEq` implementations. + +Rust comes with a standard way to automatically construct implementations of the +comparison traits through the `#[derive(...)]` attribute: + +- Derivation `PartialEq` implements `PartialEq` with a + **structural equality** providing that each subtype is `PartialEq`. +- Derivation `Eq` implements the `Eq` marker trait providing that each subtype + is `Eq`. +- Derivation `PartialOrd` implements `PartialOrd` as a + **lexicographical order** providing that each subtype is `PartialOrd`. +- Derivation `Ord` implements `Ord` as a **lexicographical order** + providing that each subtype is `Ord`. + +For instance, the short following code shows how to compare two `T1`s easily. +All the assertions hold. + +```rust +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct T1 { + a: u8, b: u8 +} + +# fn main() { +assert!(&T1 { a: 0, b: 0 } == Box::new(T1 { a: 0, b: 0 }).as_ref()); +assert!(T1 { a: 1, b: 0 } > T1 { a: 0, b: 0 }); +assert!(T1 { a: 1, b: 1 } > T1 { a: 1, b: 0 }); +# println!("all tests passed."); +# } +``` + +> **Warning** +> +> Derivation of comparison traits for compound types depends on the +> **field order**, and not on their names. +> +> First, it means that changing the order of declaration of two fields change +> the resulting lexicographical order. For instance, provided this second +> ordered type: +> +> ```rust,noplaypen +> #[derive(PartialEq, Eq, PartialOrd, Ord)] +> struct T2{ +> b: u8, a: u8 +> }; +> ``` +> +> we have `T1 {a: 1, b: 0} > T1 {a: 0, b: 1}` but +> `T2 {a: 1, b: 0} < T2 {a: 0, b: 1}`. +> +> Second, if one of the underlying comparison panics, the order may change the +> result due to the use of short-circuit logic in the automatic implementation. +> +> For enums, the derived comparisons depends first on the **variant order** then +> on the field order. + +Despite the ordering caveat, derived comparisons are a lot less error-prone +than manual ones and makes code shorter and easier to maintain. + +> **Recommendation {{#check LANG-CMP-DERIVE | Derive comparison traits when possible}}** +> +> In a secure Rust development, the implementation of standard comparison traits +> should be automatically derived with `#[derive(...)]` when structural equality +> and lexicographical comparison is needed. Any manual implementation of +> standard comparison traits should be documented and justified. diff --git a/src/en/05_central_traits.md b/src/en/05_central_traits.md new file mode 100644 index 00000000..eaa43c61 --- /dev/null +++ b/src/en/05_central_traits.md @@ -0,0 +1,59 @@ +# Central traits + +## `Drop` trait, the destructor + +Types implement the trait `std::ops::Drop` to perform some operations when the +memory associated with a value of this type is to be reclaimed. `Drop` is the +Rust equivalent of a destructor in C++ or a finalizer in Java. + +Dropping is done recursively from the outer value to the inner values. +When a value goes out of scope (or is explicitly dropped with `std::mem::drop`), +the value is dropped in two steps. The first step happens only if the type of +this value implements `Drop`. It consists in calling the `drop` method on it. +The second step consists in repeating the dropping process recursively on any +field the value contains. Note that a `Drop` implementation is +**only responsible for the outer value**. + +First and foremost, implementing `Drop` should not be systematic. +It is only needed if the type requires some destructor logic. In fact, `Drop` is +typically used to release external resources (network connections, files, etc.) +or to release memory (e.g. in smart pointers such as `Box` or `Rc`). +As a result, `Drop` trait implementations are likely to contain `unsafe` code +blocks as well as other security-critical operations. + +> **Recommendation {{#check LANG-DROP | Justify `Drop` implementation}}** +> +> In a Rust secure development, the implementation of the `std::ops::Drop` trait +> should be justified, documented and peer-reviewed. + +Second, Rust type system only ensures memory safety and, from the type system's +standpoint, missing drops is allowed. In fact, several things may lead to +missing drops, such as: + +- a reference cycle (for instance, with `Rc` or `Arc`), +- an explicit call to `std::mem::forget` (or `core::mem::forget`) (see paragraph + on [Forget and memory leaks](05_memory.html#forget-and-memory-leaks), +- a panic in drop, +- program aborts (and panics when abort-on-panic is on). + + +And missing drops may lead to exposing sensitive data or to lock limited +resources leading to unavailability issues. + +> **Rule {{#check LANG-DROP-NO-PANIC | Do not panic in `Drop` implementation}}** +> +> In a Rust secure development, the implementation of the `std::ops::Drop` trait +> must not panic. + +Beside panics, secure-critical drop should be protected. + +> **Rule {{#check LANG-DROP-NO-CYCLE | Do not allow cycles of reference-counted `Drop`}}** +> +> Value whose type implements `Drop` must not be embedded directly or indirectly +> in a cycle of reference-counted references. + +> **Recommendation {{#check LANG-DROP-SEC | Do not rely only on `Drop` to ensure security}}** +> +> Ensuring security operations at the end of some treatment (such as key erasure +> at the end of a cryptographic encryption) should not rely only on the `Drop` +> trait implementation. diff --git a/src/en/05_errors.md b/src/en/05_errors.md new file mode 100644 index 00000000..76358519 --- /dev/null +++ b/src/en/05_errors.md @@ -0,0 +1,86 @@ +# Error handling + + + +The `Result` type is the preferred way of handling functions that can fail. +A `Result` object must be tested, and never ignored. + +> **Recommendation {{#check LANG-ERRWRAP | Implement custom `Error` type, wrapping all possible errors}}** +> +> A crate can implement its own `Error` type, wrapping all possible errors. +> It must be careful to make this type exception-safe (RFC 1236), and implement +> `Error + Send + Sync + 'static` as well as `Display`. + +> **Recommendation {{#check LANG-ERRDO | Use the `?` operator and do not use the `try!` macro}}** +> +> The `?` operator should be used to improve readability of code. +> The `try!` macro should not be used. + +Third-party crates may be used to facilitate error handling. Most of them +(notably [failure], [snafu], [thiserror]) address the creation of new custom +error types that implement the necessary traits and allow wrapping other +errors. + +Another approach (notably proposed in the [anyhow] crate) consists in an automatic +wrapping of errors into a single universal error type. Such wrappers should not +be used in libraries and complex systems because they do not allow developers to +provide context to the wrapped error. + +[failure]: https://crates.io/crates/failure +[snafu]: https://crates.io/crates/snafu +[thiserror]: https://crates.io/crates/thiserror +[anyhow]: https://crates.io/crates/anyhow + +## Panics + +Explicit error handling (`Result`) should always be preferred instead of calling +`panic`. The cause of the error should be available, and generic errors should +be avoided. + +Crates providing libraries should never use functions or instructions that can +fail and cause the code to panic. + +Common patterns that can cause panics are: + +- using `unwrap` or `expect`, +- using `assert`, +- an unchecked access to an array, +- integer overflow (in debug mode), +- division by zero, +- large allocations, +- string formatting using `format!`. + +> **Rule {{#check LANG-NOPANIC | Don't use functions that can cause `panic!`}}** +> +> Functions or instructions that can cause the code to panic at runtime must not +> be used. + +> **Rule {{#check LANG-ARRINDEXING | Test properly array indexing or use the `get` method}}** +> +> Array indexing must be properly tested, or the `get` method should be used to +> return an `Option`. + + + + +## FFI and panics + +When calling Rust code from another language (for ex. C), the Rust code must +be careful to never panic. +Stack unwinding from Rust code into foreign code results in undefined behavior. + +> **Rule {{#check LANG-FFIPANIC | Handle correctly `panic!` in FFI}}** +> +> Rust code called from FFI must either ensure the function cannot panic, or use +> `catch_unwind` or the `std::panic` module to ensure the rust code will not +> abort or return in an unstable state. + +Note that `catch_unwind` will only catch unwinding panics, not those that abort +the process. diff --git a/src/en/05_guarantees.md b/src/en/05_guarantees.md new file mode 100644 index 00000000..8a3d905a --- /dev/null +++ b/src/en/05_guarantees.md @@ -0,0 +1,33 @@ +# Language guarantees + +## Undefined Behaviors (*UB*) + +> The behavior of a program is *undefined* when its semantics is not described in the Rust language. + +The existence of UB is considered an [error](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.general). + +For example, dereferencing the null pointer is a *UB*. On the other hand, `unwrap`ing the `None` object is well defined because it is the language that processes this error (by launching a panic). + +The current list of *UBs* is given in the language [reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). Notice the following guarantees: + +* No dereference of pointer to an unallocated or unaligned memory address (dangling pointer), which implies + * No buffer overflow + * No access to freed memory + * No non-aligned access +* The pointed values are [consistent](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.invalid) with the pointer's type. For example, a value pointed at by a boolean pointer will be byte of value 1 or 0. +* Respect of [aliasing rules](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.alias) (see also [nomicon](https://doc.rust-lang.org/nomicon/aliasing.html)): a mutable reference cannot be shared. +* No concurrent access (reading/writing is not possible while writing) to the same memory address ([data race](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.race), see also [nomicon](https://doc.rust-lang.org/nomicon/races.html)) + +## Rust guarantees + +> The language paradigm is to ensure the absence of a UB in a program using only the non-*unsafe* part of Rust. + +However, the language does not prevent + +* resource leaks (memory, IO, ...), +* numeric overflows. + +## References + +* https://doc.rust-lang.org/reference/unsafety.html +* https://doc.rust-lang.org/nomicon/what-unsafe-does.html diff --git a/src/en/05_integer.md b/src/en/05_integer.md new file mode 100644 index 00000000..e8594ee2 --- /dev/null +++ b/src/en/05_integer.md @@ -0,0 +1,48 @@ +# Integer operations + +## Integer overflows + +Although some verification is performed by Rust regarding potential integer +overflows, precautions should be taken when executing arithmetic operations on +integers. + +In particular, it should be noted that using debug or release compilation +profile changes integer overflow behavior. In debug configuration, overflow +cause the termination of the program (`panic`), whereas in the release +configuration the computed value silently wraps around the maximum value that +can be stored. + +This last behavior can be made explicit by using the `Wrapping` generic type, +or the `overflowing_` and `wrapping_` operations on integers +(the `` part being `add`, `mul`, `sub`, `shr`, etc.). + +```rust +use std::num::Wrapping; +# use std::panic; + +# fn main() { +let x: u8 = 242; + +# let result = panic::catch_unwind(|| { +println!("{}", x + 50); // panics in debug, prints 36 in release. +# }); +# if result.is_err() { println!("panic"); } +println!("{}", x.overflowing_add(50).0); // always prints 36. +println!("{}", x.wrapping_add(50)); // always prints 36. +println!("{}", Wrapping(x) + Wrapping(50)); // always prints 36. + +// always panics: +let (res, c) = x.overflowing_add(50); +# let result = panic::catch_unwind(|| { +if c { panic!("custom error"); } +else { println!("{}", res); } +# }); +# if result.is_err() { println!("panic"); } +# } +``` + +> **Rule {{#check LANG-ARITH | Use appropriate arithmetic operations regarding potential overflows}}** +> +> When assuming that an arithmetic operation can produce an overflow, the +> specialized functions `overflowing_`, `wrapping_`, or the +> `Wrapping` type must be used. diff --git a/src/en/05_macros.md b/src/en/05_macros.md new file mode 100644 index 00000000..509cd316 --- /dev/null +++ b/src/en/05_macros.md @@ -0,0 +1,8 @@ +# Macros + + + + \ No newline at end of file diff --git a/src/en/05_naming.md b/src/en/05_naming.md new file mode 100644 index 00000000..07d8dcc7 --- /dev/null +++ b/src/en/05_naming.md @@ -0,0 +1,30 @@ +# Naming + +As of now, the standard library is the de facto standard for naming things in +the Rust world. However, an effort has been made to formalize it, first in +[RFC 430], then in the [Rust API Guidelines]. + +The basic rule consists in using : + +- `UpperCamelCase` for types, traits, enum variants, +- `snake_case` for functions, methods, macros, variables and modules, +- `SCREAMING_SNAKE_CASE` for statics and constants, +- `'lowercase` for lifetimes. + +The [Rust API Guidelines] also prescribes more precise naming conventions for +some particular constructions: + +- (C-CONV) for conversion methods (`as_`, `to_`, `into_`), +- (C-GETTER) for getters, +- (C-ITER) for iterator-producing methods, +- (C-ITER-TY) for iterator types, +- (C-FEATURE) for feature naming, +- (C-WORD-ORDER) for word order consistency. + +> **Rule {{#check LANG-NAMING | Respect naming conventions}}** +> +> Development of a secure application must follow the naming conventions +> outlined in the [Rust API Guidelines]. + +[rfc 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md +[rust api guidelines]: https://rust-lang.github.io/api-guidelines/ diff --git a/src/en/05_typesystem.md b/src/en/05_typesystem.md index 7b631545..c375b06d 100644 --- a/src/en/05_typesystem.md +++ b/src/en/05_typesystem.md @@ -8,295 +8,3 @@ misunderstanding of which code is actually executed when implementing complex patterns with traits). --> -## Standard library traits - -### `Drop` trait, the destructor - -Types implement the trait `std::ops::Drop` to perform some operations when the -memory associated with a value of this type is to be reclaimed. `Drop` is the -Rust equivalent of a destructor in C++ or a finalizer in Java. - -Dropping is done recursively from the outer value to the inner values. -When a value goes out of scope (or is explicitly dropped with `std::mem::drop`), -the value is dropped in two steps. The first step happens only if the type of -this value implements `Drop`. It consists in calling the `drop` method on it. -The second step consists in repeating the dropping process recursively on any -field the value contains. Note that a `Drop` implementation is -**only responsible for the outer value**. - -First and foremost, implementing `Drop` should not be systematic. -It is only needed if the type requires some destructor logic. In fact, `Drop` is -typically used to release external resources (network connections, files, etc.) -or to release memory (e.g. in smart pointers such as `Box` or `Rc`). -As a result, `Drop` trait implementations are likely to contain `unsafe` code -blocks as well as other security-critical operations. - -> **Recommendation {{#check LANG-DROP | Justify `Drop` implementation}}** -> -> In a Rust secure development, the implementation of the `std::ops::Drop` trait -> should be justified, documented and peer-reviewed. - -Second, Rust type system only ensures memory safety and, from the type system's -standpoint, missing drops is allowed. In fact, several things may lead to -missing drops, such as: - -- a reference cycle (for instance, with `Rc` or `Arc`), -- an explicit call to `std::mem::forget` (or `core::mem::forget`) (see paragraph - on [Forget and memory leaks](05_memory.html#forget-and-memory-leaks), -- a panic in drop, -- program aborts (and panics when abort-on-panic is on). - - -And missing drops may lead to exposing sensitive data or to lock limited -resources leading to unavailability issues. - -> **Rule {{#check LANG-DROP-NO-PANIC | Do not panic in `Drop` implementation}}** -> -> In a Rust secure development, the implementation of the `std::ops::Drop` trait -> must not panic. - -Beside panics, secure-critical drop should be protected. - -> **Rule {{#check LANG-DROP-NO-CYCLE | Do not allow cycles of reference-counted `Drop`}}** -> -> Value whose type implements `Drop` must not be embedded directly or indirectly -> in a cycle of reference-counted references. - -> **Recommendation {{#check LANG-DROP-SEC | Do not rely only on `Drop` to ensure security}}** -> -> Ensuring security operations at the end of some treatment (such as key erasure -> at the end of a cryptographic encryption) should not rely only on the `Drop` -> trait implementation. - -### `Send` and `Sync` traits - -The `Send` and `Sync` traits (defined in `std::marker` or `core::marker`) are -marker traits used to ensure the safety of concurrency in Rust. When implemented -correctly, they allow the Rust compiler to guarantee the absence of data races. -Their semantics is as follows: - -- A type is `Send` if it is safe to send (move) it to another thread. -- A type is `Sync` if it is safe to share a immutable reference to it with - another thread. - -Both traits are _unsafe traits_, i.e., the Rust compiler does not verify in any -way that they are implemented correctly. The danger is real: an incorrect -implementation may lead to **undefined behavior**. - -Fortunately, in most cases, one does not need to implement it. In Rust, -almost all primitive types are `Send` and `Sync`, and for most compound types -the implementation is automatically provided by the Rust compiler. -Notable exceptions are: - -- Raw pointers are neither `Send` nor `Sync` because they offer no safety - guards. -- `UnsafeCell` is not `Sync` (and as a result `Cell` and `RefCell` aren't - either) because they offer interior mutability (mutably shared value). -- `Rc` is neither `Send` nor `Sync` because the reference counter is shared and - unsynchronized. - -Automatic implementation of `Send` (resp. `Sync`) occurs for a compound type -(structure or enumeration) when all fields have `Send` types (resp. `Sync` -types). Using an unstable feature (as of Rust 1.37.0), one can block the -automatic implementation of those traits with a manual -_negative implementation_: - -```rust,ignore,noplaypen -#![feature(option_builtin_traits)] - -struct SpecialType(u8); -impl !Send for SpecialType {} -impl !Sync for SpecialType {} -``` - -The negative implementation of `Send` or `Sync` are also used in the standard -library for the exceptions, and are automatically implemented when appropriate. -As a result, the generated documentation is always explicit: a type implements -either `Send` or `!Send` (resp. `Sync` or `!Sync`). - -As a stable alternative to negative implementation, one can use a `PhantomData` -field: - -```rust,noplaypen -# use std::marker::PhantomData; -# -struct SpecialType(u8, PhantomData<*const ()>); -``` - -> **Recommendation {{#check LANG-SYNC-TRAITS | Justify `Send` and `Sync` implementation}}** -> -> In a Rust secure development, the manual implementation of the `Send` and -> `Sync` traits should be avoided and, if necessary, should be justified, -> documented and peer-reviewed. - -### Comparison traits (`PartialEq`, `Eq`, `PartialOrd`, `Ord`) - -Comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) in Rust relies on four standard -traits available in `std::cmp` (or `core::cmp` for `no_std` compilation): - -- `PartialEq` that defines a partial equivalence between - objects of types `Self` and `Rhs`, -- `PartialOrd` that defines a partial order between objects of types - `Self` and `Rhs`, -- `Eq` that defines a total equivalence between objects of the same - type. It is only a marker trait that requires `PartialEq`! -- `Ord` that defines the total order between objects of the same type. - It requires that `PartialOrd` is implemented. - -As documented in the standard library, Rust assumes **a lot of invariants** -about the implementations of those traits: - -- For `PartialEq` - - - *Internal consistency*: `a.ne(b)` is equivalent to `!a.eq(b)`, i.e., `ne` is - the strict inverse of `eq`. The default implementation of `ne` is precisely - that. - - - *Symmetry*: `a.eq(b)` and `b.eq(a)`, are equivalent. From the developer's - point of view, it means: - - - `PartialEq` is implemented for type `A` (noted `A: PartialEq`), - - `PartialEq` is implemented for type `B` (noted `B: PartialEq`), - - both implementations are consistent with each other. - - - *Transitivity*: `a.eq(b)` and `b.eq(c)` implies `a.eq(c)`. It means that: - - - `A: PartialEq`, - - `B: PartialEq`, - - `A: PartialEq`, - - the three implementations are consistent with each other (and their - symmetric implementations). - -- For `Eq` - - - `PartialEq` is implemented. - - - *Reflexivity*: `a.eq(a)`. This stands for `PartialEq` (`Eq` does not - provide any method). - -- For `PartialOrd` - - - *Equality consistency*: - `a.eq(b)` is equivalent to `a.partial_cmp(b) == Some(std::ordering::Eq)`. - - - *Internal consistency*: - - - `a.lt(b)` iff `a.partial_cmp(b) == Some(std::ordering::Less)`, - - `a.gt(b)` iff `a.partial_cmp(b) == Some(std::ordering::Greater)`, - - `a.le(b)` iff `a.lt(b) || a.eq(b)`, - - `a.ge(b)` iff `a.gt(b) || a.eq(b)`. - - Note that by only defining `partial_cmp`, the internal consistency is - guaranteed by the default implementation of `lt`, `le`, `gt`, and `ge`. - - - *Antisymmetry*: `a.lt(b)` (respectively `a.gt(b)`) implies `b.gt(a)` - (respectively, `b.lt(b)`). From the developer's standpoint, it also means: - - - `A: PartialOrd`, - - `B: PartialOrd`, - - both implementations are consistent with each other. - - - *Transitivity*: `a.lt(b)` and `b.lt(c)` implies `a.lt(c)` (also with `gt`, - `le` and `ge`). It also means: - - - `A: PartialOrd`, - - `B: PartialOrd`, - - `A: PartialOrd`, - - the implementations are consistent with each other (and their symmetric). - -- For `Ord` - - - `PartialOrd` - - - *Totality*: `a.partial_cmp(b) != None` always. In other words, - exactly one of `a.eq(b)`, `a.lt(b)`, `a.gt(b)` is true. - - - *Consistency with `PartialOrd`*: `Some(a.cmp(b)) == a.partial_cmp(b)`. - -The compiler do not check any of those requirements except for the type checking -itself. However, comparisons are critical because they intervene both in -liveness critical systems such as schedulers and load balancers, and in -optimized algorithms that may use `unsafe` blocks. -In the first use, a bad ordering may lead to availability issues such as -deadlocks. -In the second use, it may lead to classical security issues linked to memory -safety violations. That is again a factor in the practice of limiting the use -of `unsafe` blocks. - -> **Rule {{#check LANG-CMP-INV | Respect the invariants of standard comparison traits}}** -> -> In a Rust secure development, the implementation of standard comparison traits -> must respect the invariants described in the documentation. - -> **Recommendation {{#check LANG-CMP-DEFAULTS | Use the default method implementation of standard comparison traits}}** -> -> In a Rust secure development, the implementation of standard comparison traits -> should only define methods with no default implementation, so as to reduce -> the risk of violating the invariants associated with the traits. - -There is a Clippy lint to check that `PartialEq::ne` is not defined in -`PartialEq` implementations. - -Rust comes with a standard way to automatically construct implementations of the -comparison traits through the `#[derive(...)]` attribute: - -- Derivation `PartialEq` implements `PartialEq` with a - **structural equality** providing that each subtype is `PartialEq`. -- Derivation `Eq` implements the `Eq` marker trait providing that each subtype - is `Eq`. -- Derivation `PartialOrd` implements `PartialOrd` as a - **lexicographical order** providing that each subtype is `PartialOrd`. -- Derivation `Ord` implements `Ord` as a **lexicographical order** - providing that each subtype is `Ord`. - -For instance, the short following code shows how to compare two `T1`s easily. -All the assertions hold. - -```rust -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct T1 { - a: u8, b: u8 -} - -# fn main() { -assert!(&T1 { a: 0, b: 0 } == Box::new(T1 { a: 0, b: 0 }).as_ref()); -assert!(T1 { a: 1, b: 0 } > T1 { a: 0, b: 0 }); -assert!(T1 { a: 1, b: 1 } > T1 { a: 1, b: 0 }); -# println!("all tests passed."); -# } -``` - -> **Warning** -> -> Derivation of comparison traits for compound types depends on the -> **field order**, and not on their names. -> -> First, it means that changing the order of declaration of two fields change -> the resulting lexicographical order. For instance, provided this second -> ordered type: -> -> ```rust,noplaypen -> #[derive(PartialEq, Eq, PartialOrd, Ord)] -> struct T2{ -> b: u8, a: u8 -> }; -> ``` -> -> we have `T1 {a: 1, b: 0} > T1 {a: 0, b: 1}` but -> `T2 {a: 1, b: 0} < T2 {a: 0, b: 1}`. -> -> Second, if one of the underlying comparison panics, the order may change the -> result due to the use of short-circuit logic in the automatic implementation. -> -> For enums, the derived comparisons depends first on the **variant order** then -> on the field order. - -Despite the ordering caveat, derived comparisons are a lot less error-prone -than manual ones and makes code shorter and easier to maintain. - -> **Recommendation {{#check LANG-CMP-DERIVE | Derive comparison traits when possible}}** -> -> In a secure Rust development, the implementation of standard comparison traits -> should be automatically derived with `#[derive(...)]` when structural equality -> and lexicographical comparison is needed. Any manual implementation of -> standard comparison traits should be documented and justified. diff --git a/src/en/SUMMARY.md b/src/en/SUMMARY.md index 7ec884f7..7d67f57c 100644 --- a/src/en/SUMMARY.md +++ b/src/en/SUMMARY.md @@ -1,15 +1,30 @@ # Summary -- [Introduction](01_introduction.md) +[Introduction](01_introduction.md) + +# Lifecycle + - [Development environment](02_devenv.md) - [Libraries](03_libraries.md) -- [Language generalities](04_language.md) -- [Type system](05_typesystem.md) + +# Language + +- [Language guarantees](05_guarantees.md) +- [Naming](05_naming.md) + +- [Integer operations](05_integer.md) +- [Error handling](05_errors.md) + +- [Central traits](05_central_traits.md) - [Unsafe Rust](06_unsafe.md) - [Generalities](06_1_unsafe_generalities.md) - [Memory management](06_2_unsafe_memory.md) - [Foreign Function Interface](06_3_unsafe_ffi.md) +# Ecosystem + +- [Standard library](04_standard.md) + [Licence](LICENCE.md) diff --git a/src/fr/04_language.md b/src/fr/04_language.md deleted file mode 100644 index 7d887bb2..00000000 --- a/src/fr/04_language.md +++ /dev/null @@ -1,225 +0,0 @@ -# Généralités sur le langage - -## Garanties du langage - -### Comportements indéfinis - -> Le comportement d'un programme est *indéfini* (*UB* pour *Undefined Behavior*) lorsque sa sémantique n'est -> pas décrite dans le langage Rust. - -L'existence d'*UB* est considéré comme une [erreur](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.general). - -Par exemple le déréférencement d'un pointeur null est un *UB*. -*A contrario*, un `unwrap` sur l'objet `None` est bien *défini* car c'est le langage qui traite cette erreur -(en lançant un `panic`). - -La liste actuelle des *UB* est donnée [ici](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). -On notera les garanties suivantes : - -* Pas de déréférencement de pointeur vers une adresse mémoire non allouée (*dangling pointer*) ou non alignée, ce qui implique - * Pas de dépassement de tableau - * Pas d'accès à de la mémoire libérée - * Accès toujours aligné quelque soit la plateforme -* Les valeurs pointées sont [cohérentes](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.invalid) avec le type du pointeur. Par exemple, une valeur pointée par un pointeur booléen sera l'octet 1 ou 0. -* Respect des règles d'[*aliasing*](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.alias) (voir aussi le [nomicon](https://doc.rust-lang.org/nomicon/aliasing.html)): une référence mutable ne peux être partagée. -* Pas d'accès concurrent (un accès en lecture et un autre en écriture ou en lecture) à la même adresse mémoire ([*data race*](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.race), voir aussi le [nomicon](https://doc.rust-lang.org/nomicon/races.html)) - -### Garantie de Rust - -> La volonté du langage est d'assurer l'absence d'*UB* dans un programme utilisant uniquement la partie non *unsafe* de Rust. - -Cependant, le langage ***ne protège pas*** contre les erreurs suivantes : - -* fuites de resources (mémoire, IO, ...) ; -* dépassements numériques. - -### Références - -* https://doc.rust-lang.org/reference/unsafety.html -* https://doc.rust-lang.org/nomicon/what-unsafe-does.html - - -## Nommage - -La convention de nommage employée par la bibliothèque standard est *de facto* le -standard pour le nommage des éléments des programmes écrits en Rust. Un effort a -été fait pour formaliser ces conventions de nommage, d'abord dans la [RFC 430], -puis dans le document des *[Rust API Guidelines]*. - -La règle de base consiste à utiliser : - -- `UpperCamelCase` pour les types, traits et valeurs d'énumérations ; -- `snake_case` pour les fonctions, méthodes, macros, variables et modules ; -- `SCREAMING_SNAKE_CASE` pour les variables statiques et les constantes ; -- `'lowercase` pour les durées de vie (*lifetimes*). - -Les [Rust API Guidelines] recommandent également des conventions de nommage -plus précises pour certaines constructions particulières : - -- (C-CONV) pour les méthodes de conversion (`as_`, `to_`, `into_`) ; -- (C-GETTER) pour les accesseurs ; -- (C-ITER) pour les méthodes produisant des itérateurs ; -- (C-ITER-TY) pour les types itérateur ; -- (C-FEATURE) pour les noms de *features* ; -- (C-WORD-ORDER) pour la cohérence sur l'ordre des mots. - -> **Règle {{#check LANG-NAMING | Respect des conventions de nommage}}** -> -> Le développement d'une application sécurisée doit suivre les conventions de -> nommage décrites dans les [Rust API Guidelines]. - -[rfc 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md -[rust api guidelines]: https://rust-lang.github.io/api-guidelines/ - - - -## Dépassement d'entiers - -Bien que des vérifications soient effectuées par Rust en ce qui concerne les -potentiels dépassements d'entiers, des précautions doivent être prises lors de -l'exécution d'opérations arithmétiques sur les entiers. - -En particulier, il doit être noté que les profils de compilation *debug* et -*release* produisent des variations de comportements quant à la gestion des -dépassements d'entiers. Dans la configuration *debug*, un dépassement provoque -la terminaison du programme (`panic`), tandis que dans la configuration -*release* la valeur calculée est silencieusement tronquée en fonction de la -valeur maximum qui peut être stockée pour le type considéré. - -Ce comportement peut être rendu explicite en utilisant le type générique -`Wrapping`, ou les opérations sur les entiers `overflowing_` et -`wrapping_` (la partie `` étant remplacée par le type de calcul : -`add`, `mul`, `sub`, `shr`, etc.). - -```rust -use std::num::Wrapping; -# use std::panic; - -# fn main() { -let x: u8 = 242; - -# let result = panic::catch_unwind(|| { -println!("{}", x + 50); // panique en mode debug, affiche 36 en mode release. -# }); -# if result.is_err() { println!("panic"); } -println!("{}", x.overflowing_add(50).0); // affiche toujours 36. -println!("{}", x.wrapping_add(50)); // affiche toujours 36. -println!("{}", Wrapping(x) + Wrapping(50)); // affiche toujours 36. - -// panique toujours : -let (res, c) = x.overflowing_add(50); -# let result = panic::catch_unwind(|| { -if c { panic!("custom error"); } -else { println!("{}", res); } -# }); -# if result.is_err() { println!("panic"); } -# } -``` - -> **Règle {{#check LANG-ARITH | Utilisation des opérations arithmétiques appropriées au regard des potentiels dépassements}}** -> -> Lorsqu'une opération arithmétique peut produire un dépassement d'entier, les -> fonctions spécialisées `overflowing_`, `wrapping_` ou le type -> `Wrapping` doivent être utilisés. - -## Gestion des erreurs - - - -Le type `Result` est la façon privilégiée en Rust pour décrire le type de retour -des fonctions dont le traitement peut échouer. Un objet `Result` doit être -testé et jamais ignoré. - -> **Recommandation {{#check LANG-ERRWRAP | Mise en place d'un type `Error` personnalisé, pouvant contenir toutes les erreurs possibles}}** -> -> Une *crate* peut implanter son propre type `Error` qui peut contenir toutes -> les erreurs possibles. Des précautions supplémentaires doivent être prises : -> ce type doit être *exception-safe* (RFC 1236) et implémenter les traits -> `Error + Send + Sync + 'static` ainsi que `Display`. - -> **Recommandation {{#check LANG-ERRDO | Utilisation de l'opérateur `?` et non-utilisation de la macro `try!`}}** -> -> L'opérateur `?` doit être utilisé pour améliorer la lisibilité du code. -> La macro `try!` ne doit pas être utilisée. - -Des *crates* tierces peuvent être utilisées pour faciliter la gestion d'erreurs. -La plupart ([failure], [snafu], [thiserror]) proposent la création de types -d'erreurs personnalisées qui implémentent les traits nécessaires et permettent -l'encapsulation d'autres erreurs. - -Une autre approche (notamment proposée dans [anyhow]) consiste à envelopper -automatiquement les erreurs dans un seul type d'erreur universel. Une telle -approche ne devrait pas être utilisée dans des bibliothèques ou des systèmes -complexes parce qu'elle ne permet pas de fournir de contexte sur les erreurs -ainsi initialement enveloppées, contrairement à la première approche. - -[failure]: https://crates.io/crates/failure -[snafu]: https://crates.io/crates/snafu -[thiserror]: https://crates.io/crates/thiserror -[anyhow]: https://crates.io/crates/anyhow - -### *Panics* - -La gestion explicite des erreurs (`Result`) doit être préférée à la place de -l'utilisation de la macro `panic`. La cause de l'erreur doit être rendue -disponible, et les erreurs trop génériques doivent être évitées. - -Les *crates* fournissant des bibliothèques ne doivent pas utiliser de fonctions -ou d'instructions qui peuvent échouer en engendrant un `panic`. - -Des motifs courants de code qui provoquent des `panic` sont : - -- une utilisation de `unwrap` ou de `expect` ; -- une utilisation de `assert` ; -- un accès non vérifié à un tableau ; -- un dépassement d'entier (en mode *debug*) ; -- une division par zéro ; -- l'utilisation de `format!` pour le formatage d'une chaîne de caractères. - -> **Règle {{#check LANG-NOPANIC | Non-utilisation de fonctions qui peuvent causer des `panic`}}** -> -> Les fonctions et instructions qui peuvent causer des `panic` à l'exécution -> ne doivent pas être utilisées. - -> **Règle {{#check LANG-ARRINDEXING | Test des indices d'accès aux tableaux ou utilisation de la méthode `get`}}** -> -> L'indice d'accès à un tableau doit être testé, ou la méthode `get` doit être -> utilisée pour récupérer une `Option`. - - - - -### FFI et `panic`s - -Lorsque du code Rust est appelé depuis du code écrit dans un autre -langage (par exemple, du code C), le code Rust doit être écrit de sorte à ne -jamais pouvoir paniquer. -Dérouler (*unwinding*) depuis le code Rust vers le code étranger résulte en un -comportement indéfini. - -> **Règle {{#check LANG-FFIPANIC | Gestion correcte des `panic!` dans les FFI}}** -> -> Le code Rust appelé depuis une FFI doit soit être assuré de ne pas paniquer, -> soit utiliser `catch_unwind` ou le module `std::panic` pour s'assurer qu'il -> ne va pas abandonner un traitement puis que l'exécution retourne dans le -> langage appelant dans un état instable. - -Il est porté à l'attention du développeur que `catch_unwind` ne va traiter que -les cas de `panic`, et va préserver les abandons de processus causés par -d'autres raisons. - - - - diff --git a/src/fr/04_standard.md b/src/fr/04_standard.md new file mode 100644 index 00000000..697ea51e --- /dev/null +++ b/src/fr/04_standard.md @@ -0,0 +1,252 @@ +# Bibliothèque standard + +## Les traits `Send` et `Sync` + +Les traits `Send` et `Sync` (définis dans `std::marker` ou `core::marker`) sont +des marqueurs utilisés pour assurer la sûreté des accès concurrents en Rust. +Lorsqu'ils sont correctement implémentés, ils permettent au compilateur Rust de +garantir l'absence de problèmes d'accès concurrents. Leurs sémantiques sont +définies comme suit : + +- Un type est `Send` s’il est sûr d'envoyer (*move*) des valeurs de ce type vers + un autre fil d'exécution. +- Un type est `Sync` s’il est sûr de partager des valeurs de ce type par une + référence immutable avec un autre fil d'exécution. + +Ces deux traits sont des traits *unsafe*, c'est-à-dire que le compilateur Rust +ne vérifie d'aucune manière que leur implémentation est correcte. Le danger est +réel : une implémentation incorrecte peut mener à un **comportement indéfini**. + +Heureusement, dans la plupart des cas, il n'est pas nécessaire de fournir une +implémentation. En Rust, la quasi-totalité des types primitifs implémente +`Send` et `Sync`, et dans la majorité des cas, Rust fournit de manière +automatique une implémentation pour les types composés. Quelques exceptions +notables sont : + +- les pointeurs `raw`, qui n'implémentent ni `Send`, ni `Sync`, puisqu'ils + n'offrent aucune garantie quant à la sûreté ; +- les références `UnsafeCell`, qui n'implémentent pas `Sync` (et par extensions, + les références `Cell` et `RefCell` non plus), puisqu'elles autorisent la + mutabilité des valeurs contenues (*interior mutability*) ; +- les références `Rc`, qui n'implémentent ni `Send`, ni `Sync`, puisque les + compteurs de références seraient partagés de manière désynchronisée. + +L'implémentation automatique de `Send` (respectivement `Sync`) a lieu pour les +types composés (structures ou énumérations) lorsque tous les champs contenus +implémentent `Send` (respectivement `Sync`). Une fonctionnalité notable, mais +**instable**, de Rust (depuis 1.37.0) permet d'empêcher cette implémentation +automatique en annotant explicitement le type considéré avec une +_négation d'implementation_ : + +```rust,ignore,noplaypen +#![feature(option_builtin_traits)] + +struct SpecialType(u8); +impl !Send for SpecialType {} +impl !Sync for SpecialType {} +``` +L'implémentation négative de `Send` ou `Sync` est également utilisée dans la +bibliothèque standard pour les exceptions, et est automatiquement implémentée +lorsque cela est approprié. En résultat, la documentation générée est toujours +explicite : un type implémente soit `Send` (respectivement `Sync`), soit +`!Send` (respectivement `!Sync`). + +En guise d'alternative *stable* à l'implémentation négative, il est possible +d'utiliser un champ typé par un type fantôme (`PhantomData`) : + +```rust,noplaypen +# use std::marker::PhantomData; +# +struct SpecialType(u8, PhantomData<*const ()>); +``` + +> **Recommandation {{#check LANG-SYNC-TRAITS | Justification de l'implémentation des traits `Send` et `Sync`}}** +> +> Dans un développement sécurisé en Rust, l'implémentation manuelle des traits +> `Send` et `Sync` doit être évitée, et, si nécessaire, doit être justifiée, +> documentée et révisée par des pairs. + +## Les traits de comparaison : `PartialEq`, `Eq`, `PartialOrd`, `Ord` + +Les comparaisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) en Rust reposent sur quatre +traits de la bibliothèque standard disponibles dans `std::cmp` (ou `core::cmp` +pour une compilation avec `no_std`) : + +- `PartialEq` qui définit la relation d'équivalence partielle entre objets + de types `Self` et `Rhs` ; +- `PartialOrd` qui définit la relation d'ordre partiel entre les objets de + types `Self` et `Rhs` ; +- `Eq` qui définit la relation d'équivalence totale entre les objets du même + type. Il s'agit d'un trait de marquage qui requiert le trait + `PartialEq` ; +- `Ord` qui définit la relation d'ordre total entre les objets du même type. + Le trait `PartialOrd` est alors requis. + +Comme stipulé dans la documentation de la bibliothèque standard, Rust présuppose +**de nombreux invariants** lors de l'implémentation de ces traits : + +- Pour `PartialEq` : + + - *Cohérence interne* : `a.ne(b)` est équivalent à `!a.eq(b)`, c.-à-d., `ne` + est le strict inverse de `eq`. Cela correspond précisément à + l'implémentation par défaut de `ne`. + + - *Symétrie* : `a.eq(b)` et `b.eq(a)` sont équivalents. Du point de vue du + développeur, cela signifie que : + + - `PartialEq` est implémenté pour le type `A` (noté `A: PartialEq`). + - `PartialEq` est implémenté pour le type `B` (noté `B: PartialEq`). + - Les deux implémentations sont cohérentes l'une avec l'autre. + + - *Transitivité* : `a.eq(b)` et `b.eq(c)` impliquent `a.eq(c)`. Cela signifie + que : + + - `A: PartialEq`. + - `B: PartialEq`. + - `A: PartialEq`. + - Les trois implémentations sont cohérentes les unes avec les autres (ainsi + qu'avec leurs implémentations symétriques). + +- Pour `Eq` : + + - `PartialEq` est implémenté. + + - *Réflexivité* : `a.eq(a)`. Cela signifie que `PartialEq` est + implémenté (`Eq` ne fournit aucune méthode). + +- Pour `PartialOrd` : + + - *Consistance de la relation d'égalité* : `a.eq(b)` est équivalent à + `a.partial_cmp(b) == Some(std::ordering::Eq)`. + + - *Consistence interne* : + + - `a.lt(b)` ssi `a.partial_cmp(b) == Some(std::ordering::Less)`. + - `a.gt(b)` ssi `a.partial_cmp(b) == Some(std::ordering::Greater)`. + - `a.le(b)` ssi `a.lt(b) || a.eq(b)`. + - `a.ge(b)` ssi `a.gt(b) || a.eq(b)`. + + Il faut noter qu'en définissant seulement `partial_cmp`, la consistance + interne est garantie par les implémentations par défaut de `lt`, `le`, `gt`, + and `ge`. + + - *Antisymétrie* : `a.lt(b)` (respectivement `a.gt(b)`) implique `b.gt(a)` + (respectivement `b.lt(b)`). Du point de vue du développeur, cela signifie + que : + + - `A: PartialOrd`. + - `B: PartialOrd`. + - Les deux implémentations sont cohérentes l'une avec l'autre. + + - *Transitivité* : `a.lt(b)` et `b.lt(c)` impliquent `a.lt(c)` (également avec + `gt`, `le` et `ge`). Cela signifie que : + + - `A: PartialOrd`. + - `B: PartialOrd`. + - `A: PartialOrd`. + - Les trois implémentations sont cohérentes les unes avec les autres (et + avec leurs implémentations symétriques). + +- Pour `Ord` : + + - `PartialOrd` + + - *Totalité* : `a.partial_cmp(b) != None` est toujours vrai. En d'autres mots, + exactement une assertion parmi `a.eq(b)`, `a.lt(b)` et `a.gt(b)` est vraie. + + - *Cohérence avec `PartialOrd`*: `Some(a.cmp(b)) == a.partial_cmp(b)`. + +Le compilateur ne vérifie aucun de ces prérequis, à l'exception des +vérifications sur les types. Toutefois, les comparaisons sont des éléments +importants puisqu'elles jouent un rôle tant dans les propriétés de vivacité +des systèmes critiques comme des ordonnanceurs ou des répartiteurs de charge +que dans les algorithmes optimisés qui peuvent éventuellement utiliser des +blocs `unsafe`. Dans le premier cas d'usage, une mauvaise relation d'ordre +peut causer des problèmes de disponibilité comme des interblocages. Dans le +second cas, cela peut mener à des problèmes classiques de sécurité liés à des +violations de propriétés de sûreté mémoire. C'est là encore un atout que de +limiter au possible l'utilisation des blocs `unsafe`. + +> **Règle {{#check LANG-CMP-INV | Respect des invariants des traits de comparaison standards}}** +> +> Dans un développement sécurisé en Rust, l'implémentation des traits de +> comparaison standards doit respecter les invariants décrits dans la +> documentation. + +> **Recommandation {{#check LANG-CMP-DEFAULTS | Utilisation des implémentations par défaut des traits de comparaison standards}}** +> +> Dans un développement sécurisé en Rust, l'implémentation des traits de +> comparaison standard ne doit être effectuée que par l'implémentation des +> méthodes ne fournissant pas d'implémentation par défaut, dans le but de +> réduire le risque de violation des invariants associés auxdits traits. + +Il existe un *lint* Clippy qui permet de vérifier que `PartialEq::ne` n'est pas +défini lors d'une implémentation du trait `PartialEq`. + +Rust propose une façon de fournir automatiquement des implémentations par défaut +pour les traits de comparaison, au travers de l'attribut `#[derive(...)]` : + +- La dérivation de `PartialEq` implémente `PartialEq` avec une **égalité + structurelle** à condition que chacun des types des données membres implémente + `PartialEq`. +- La dérivation de `Eq` implémente le trait de marquage `Eq` à condition que + chacun des types des données membres implémente `Eq`. +- La dérivation de `PartialOrd` implémente `PartialOrd` comme un **ordre + lexicographique** à condition que chacun des types des données membres + implémente `PartialOrd`. +- La dérivation de `Ord` implémente `Ord` comme un **ordre lexicographique** à + condition que chacun des types des données membres implémente `Ord`. + +Par exemple, le court extrait de code suivant montre comment comparer deux +valeurs de type `T1` facilement. Toutes les assertions sont vraies. + +```rust +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct T1 { + a: u8, b: u8 +} + +# fn main() { +assert!(&T1 { a: 0, b: 0 } == Box::new(T1 { a: 0, b: 0 }).as_ref()); +assert!(T1 { a: 1, b: 0 } > T1 { a: 0, b: 0 }); +assert!(T1 { a: 1, b: 1 } > T1 { a: 1, b: 0 }); +# println!("tous les tests sont validés."); +# } +``` + +> **Attention** +> +> La dérivation des traits de comparaison pour les types composites dépend de +> **l'ordre de déclaration des champs** et non de leur nom. +> +> D'abord, cela implique que changer l'ordre des champs modifie l'ordre des +> valeurs. Par exemple, en considérant le type suivant : +> +> ```rust,noplaypen +> #[derive(PartialEq, Eq, PartialOrd, Ord)] +> struct T2{ +> b: u8, a: u8 +> }; +> ``` +> +> on a `T1 {a: 1, b: 0} > T1 {a: 0, b: 1}` mais +> `T2 {a: 1, b: 0} < T2 {a: 0, b: 1}`. +> +> Ensuite, si une comparaison sous-jacente provoque un `panic`, l'ordre peut +> changer le résultat à cause de l'utilisation d'un opérateur logique court- +> circuitant dans l'implémentation automatique. +> +> Pour les énumérations, les comparaisons dérivées dépendent d'abord de +> **l'ordre des variants**, puis de l'ordre des champs. + +En dépit de ces avertissements sur les ordres dérivés, les comparaisons dérivées +automatiquement sont bien moins sujettes à erreurs que des implémentations +manuelles, et rendent le code plus court et plus simple à maintenir. + +> **Recommandation {{#check LANG-CMP-DERIVE | Dérivation des traits de comparaison lorsque c'est possible}}** +> +> Dans un développement sécurisé en Rust, l'implémentation des traits de +> comparaison standard doit être automatiquement dérivée à l'aide de +> `#[derive(...)]` lorsque l'égalité structurelle et l'ordre lexicographique +> sont nécessaires. Toute implémentation manuelle d'un trait de comparaison +> standard doit être justifiée et documentée. \ No newline at end of file diff --git a/src/fr/05_central_traits.md b/src/fr/05_central_traits.md new file mode 100644 index 00000000..53f4f5d6 --- /dev/null +++ b/src/fr/05_central_traits.md @@ -0,0 +1,66 @@ +# Traits centraux + +Implémenter ces traits modifie la sémantique d'exécution du langage. + +## Trait `Drop` : le destructeur + +Les types implémentent le trait `std::ops::Drop` dans le but d'effectuer +certaines opérations lorsque la mémoire associée à une valeur est réclamée. +`Drop` est l'équivalent Rust d'un destructeur en C++ ou un finaliseur en Java. + +`Drop` agit récursivement, depuis la valeur externe vers les valeurs imbriquées. +Lorsqu'une valeur sort du scope (ou est explicitement relâchée avec +`std::mem::drop`), elle est relâchée en deux étapes. La première étape a lieu +uniquement si le type de la valeur en question implémente le trait `Drop` et +consiste en l'appel de la méthode `drop`. La seconde étape consiste en la +répétition de processus de *drop* récursivement sur tous les champs que contient +la valeur. Il est à noter que l'implémentation de `Drop` est +*responsable uniquement de la valeur extérieure*. + +Tout d'abord, l'implémentation de `Drop` ne doit pas être systématique. Elle est +nécessaire uniquement lorsque le type requiert un traitement logique à la +destruction. `Drop` est typiquement utilisé dans le cas du relâchement des +ressources externes (connexions réseau, fichier, etc.) ou de ressources mémoire +complexes (*smart pointers* comme les `Box` ou les `Rc` par exemple). Au +final, il est probable que l'implémentation du trait `Drop` contienne des blocs +`unsafe` ainsi que d'autres opérations critiques du point de vue de la sécurité. + +> **Recommandation {{#check LANG-DROP | Justification de l'implémentation du trait `Drop`}}** +> +> Dans un développement sécurisé en Rust, l'implémentation du trait +> `std::ops::Drop` doit être justifiée, documentée et examinée par des pairs. + +Ensuite, le système de types de Rust assure seulement la sûreté mémoire et, +du point de vue du typage, des `drop`s peuvent tout à fait être manqués. +Plusieurs situations peuvent mener à manquer des `drop`s, comme : + +- un cycle dans la référence (par exemple avec `Rc` ou `Arc`) ; +- un appel explicite à `std::mem::forget` (ou `core::mem::forget`) (voir + paragraphe à propos de [`forget` et des fuites de mémoire](05_memory.html#forget-et-fuites-de-mémoire) ; +- un `panic` dans un `drop` ; +- un arrêt du programme (et un `panic` lorsque `abort-on-panic` est activé). + +Les `drop`s manqués peuvent mener à l'exposition de données sensibles ou bien +encore à l'épuisement de ressources limitées et par là même à des problèmes +d'indisponibilité. + +> **Règle {{#check LANG-DROP-NO-PANIC | Absence de `panic` dans l'implémentation de `Drop`}}** +> +> Dans un développement sécurisé en Rust, l'implémentation du trait +> `std::ops::Drop` ne doit pas causer de `panic`. + +En plus des `panic`s, les `drop`s contenant du code critique doivent être +protégés. + +> **Règle {{#check LANG-DROP-NO-CYCLE | Absence de cycles de références avec valeurs `Drop`ables}}** +> +> Les valeurs dont le type implémente `Drop` ne doivent pas être incluses, +> directement ou indirectement, dans un cycle de références à compteurs. + + + +> **Recommandation {{#check LANG-DROP-SEC | Sécurité assurée par d'autres mécanismes en plus du trait `Drop`}}** +> +> Certaines opérations liées à la sécurité d'une application à la fin d'un +> traitement (comme l'effacement de secrets cryptographiques par exemple) ne +> doivent pas reposer uniquement sur l'implémentation du trait `Drop`. diff --git a/src/fr/05_errors.md b/src/fr/05_errors.md new file mode 100644 index 00000000..d2c3c741 --- /dev/null +++ b/src/fr/05_errors.md @@ -0,0 +1,101 @@ +# Gestion des erreurs + + + +Le type `Result` est la façon privilégiée en Rust pour décrire le type de retour +des fonctions dont le traitement peut échouer. Un objet `Result` doit être +testé et jamais ignoré. + +> **Recommandation {{#check LANG-ERRWRAP | Mise en place d'un type `Error` personnalisé, pouvant contenir toutes les erreurs possibles}}** +> +> Une *crate* peut implanter son propre type `Error` qui peut contenir toutes +> les erreurs possibles. Des précautions supplémentaires doivent être prises : +> ce type doit être *exception-safe* (RFC 1236) et implémenter les traits +> `Error + Send + Sync + 'static` ainsi que `Display`. + +> **Recommandation {{#check LANG-ERRDO | Utilisation de l'opérateur `?` et non-utilisation de la macro `try!`}}** +> +> L'opérateur `?` doit être utilisé pour améliorer la lisibilité du code. +> La macro `try!` ne doit pas être utilisée. + +Des *crates* tierces peuvent être utilisées pour faciliter la gestion d'erreurs. +La plupart ([failure], [snafu], [thiserror]) proposent la création de types +d'erreurs personnalisées qui implémentent les traits nécessaires et permettent +l'encapsulation d'autres erreurs. + +Une autre approche (notamment proposée dans [anyhow]) consiste à envelopper +automatiquement les erreurs dans un seul type d'erreur universel. Une telle +approche ne devrait pas être utilisée dans des bibliothèques ou des systèmes +complexes parce qu'elle ne permet pas de fournir de contexte sur les erreurs +ainsi initialement enveloppées, contrairement à la première approche. + +[failure]: https://crates.io/crates/failure +[snafu]: https://crates.io/crates/snafu +[thiserror]: https://crates.io/crates/thiserror +[anyhow]: https://crates.io/crates/anyhow + +## *Panics* + +La gestion explicite des erreurs (`Result`) doit être préférée à la place de +l'utilisation de la macro `panic`. La cause de l'erreur doit être rendue +disponible, et les erreurs trop génériques doivent être évitées. + +Les *crates* fournissant des bibliothèques ne doivent pas utiliser de fonctions +ou d'instructions qui peuvent échouer en engendrant un `panic`. + +Des motifs courants de code qui provoquent des `panic` sont : + +- une utilisation de `unwrap` ou de `expect` ; +- une utilisation de `assert` ; +- un accès non vérifié à un tableau ; +- un dépassement d'entier (en mode *debug*) ; +- une division par zéro ; +- l'utilisation de `format!` pour le formatage d'une chaîne de caractères. + +> **Règle {{#check LANG-NOPANIC | Non-utilisation de fonctions qui peuvent causer des `panic`}}** +> +> Les fonctions et instructions qui peuvent causer des `panic` à l'exécution +> ne doivent pas être utilisées. + +> **Règle {{#check LANG-ARRINDEXING | Test des indices d'accès aux tableaux ou utilisation de la méthode `get`}}** +> +> L'indice d'accès à un tableau doit être testé, ou la méthode `get` doit être +> utilisée pour récupérer une `Option`. + + + + +## FFI et `panic`s + +Lorsque du code Rust est appelé depuis du code écrit dans un autre +langage (par exemple, du code C), le code Rust doit être écrit de sorte à ne +jamais pouvoir paniquer. +Dérouler (*unwinding*) depuis le code Rust vers le code étranger résulte en un +comportement indéfini. + +> **Règle {{#check LANG-FFIPANIC | Gestion correcte des `panic!` dans les FFI}}** +> +> Le code Rust appelé depuis une FFI doit soit être assuré de ne pas paniquer, +> soit utiliser `catch_unwind` ou le module `std::panic` pour s'assurer qu'il +> ne va pas abandonner un traitement puis que l'exécution retourne dans le +> langage appelant dans un état instable. + +Il est porté à l'attention du développeur que `catch_unwind` ne va traiter que +les cas de `panic`, et va préserver les abandons de processus causés par +d'autres raisons. + + + + diff --git a/src/fr/05_guarantees.md b/src/fr/05_guarantees.md new file mode 100644 index 00000000..ebbc1ee1 --- /dev/null +++ b/src/fr/05_guarantees.md @@ -0,0 +1,39 @@ +# Garanties du langage + + +## Comportements indéfinis + +> Le comportement d'un programme est *indéfini* (*UB* pour *Undefined Behavior*) lorsque sa sémantique n'est +> pas décrite dans le langage Rust. + +L'existence d'*UB* est considéré comme une [erreur](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.general). + +Par exemple le déréférencement d'un pointeur null est un *UB*. +*A contrario*, un `unwrap` sur l'objet `None` est bien *défini* car c'est le langage qui traite cette erreur +(en lançant un `panic`). + +La liste actuelle des *UB* est donnée [ici](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). +On notera les garanties suivantes : + +* Pas de déréférencement de pointeur vers une adresse mémoire non allouée (*dangling pointer*) ou non alignée, ce qui implique + * Pas de dépassement de tableau + * Pas d'accès à de la mémoire libérée + * Accès toujours aligné quelque soit la plateforme +* Les valeurs pointées sont [cohérentes](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.invalid) avec le type du pointeur. Par exemple, une valeur pointée par un pointeur booléen sera l'octet 1 ou 0. +* Respect des règles d'[*aliasing*](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.alias) (voir aussi le [nomicon](https://doc.rust-lang.org/nomicon/aliasing.html)): une référence mutable ne peux être partagée. +* Pas d'accès concurrent (un accès en lecture et un autre en écriture ou en lecture) à la même adresse mémoire ([*data race*](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.race), voir aussi le [nomicon](https://doc.rust-lang.org/nomicon/races.html)) + +## Garantie de Rust + +> La volonté du langage est d'assurer l'absence d'*UB* dans un programme utilisant uniquement la partie non *unsafe* de Rust. + +Cependant, le langage ***ne protège pas*** contre les erreurs suivantes : + +* fuites de resources (mémoire, IO, ...) ; +* dépassements numériques. + +## Références + +* https://doc.rust-lang.org/reference/unsafety.html +* https://doc.rust-lang.org/nomicon/what-unsafe-does.html + diff --git a/src/fr/05_integer.md b/src/fr/05_integer.md new file mode 100644 index 00000000..c359f8bf --- /dev/null +++ b/src/fr/05_integer.md @@ -0,0 +1,50 @@ +# Traitements des entiers + +## Dépassement d'entiers + +Bien que des vérifications soient effectuées par Rust en ce qui concerne les +potentiels dépassements d'entiers, des précautions doivent être prises lors de +l'exécution d'opérations arithmétiques sur les entiers. + +En particulier, il doit être noté que les profils de compilation *debug* et +*release* produisent des variations de comportements quant à la gestion des +dépassements d'entiers. Dans la configuration *debug*, un dépassement provoque +la terminaison du programme (`panic`), tandis que dans la configuration +*release* la valeur calculée est silencieusement tronquée en fonction de la +valeur maximum qui peut être stockée pour le type considéré. + +Ce comportement peut être rendu explicite en utilisant le type générique +`Wrapping`, ou les opérations sur les entiers `overflowing_` et +`wrapping_` (la partie `` étant remplacée par le type de calcul : +`add`, `mul`, `sub`, `shr`, etc.). + +```rust +use std::num::Wrapping; +# use std::panic; + +# fn main() { +let x: u8 = 242; + +# let result = panic::catch_unwind(|| { +println!("{}", x + 50); // panique en mode debug, affiche 36 en mode release. +# }); +# if result.is_err() { println!("panic"); } +println!("{}", x.overflowing_add(50).0); // affiche toujours 36. +println!("{}", x.wrapping_add(50)); // affiche toujours 36. +println!("{}", Wrapping(x) + Wrapping(50)); // affiche toujours 36. + +// panique toujours : +let (res, c) = x.overflowing_add(50); +# let result = panic::catch_unwind(|| { +if c { panic!("custom error"); } +else { println!("{}", res); } +# }); +# if result.is_err() { println!("panic"); } +# } +``` + +> **Règle {{#check LANG-ARITH | Utilisation des opérations arithmétiques appropriées au regard des potentiels dépassements}}** +> +> Lorsqu'une opération arithmétique peut produire un dépassement d'entier, les +> fonctions spécialisées `overflowing_`, `wrapping_` ou le type +> `Wrapping` doivent être utilisés. diff --git a/src/fr/05_macros.md b/src/fr/05_macros.md new file mode 100644 index 00000000..509cd316 --- /dev/null +++ b/src/fr/05_macros.md @@ -0,0 +1,8 @@ +# Macros + + + + \ No newline at end of file diff --git a/src/fr/05_naming.md b/src/fr/05_naming.md new file mode 100644 index 00000000..0296ad7c --- /dev/null +++ b/src/fr/05_naming.md @@ -0,0 +1,32 @@ +# Nommage + +La convention de nommage employée par la bibliothèque standard est *de facto* le +standard pour le nommage des éléments des programmes écrits en Rust. Un effort a +été fait pour formaliser ces conventions de nommage, d'abord dans la [RFC 430], +puis dans le document des *[Rust API Guidelines]*. + +La règle de base consiste à utiliser : + +- `UpperCamelCase` pour les types, traits et valeurs d'énumérations ; +- `snake_case` pour les fonctions, méthodes, macros, variables et modules ; +- `SCREAMING_SNAKE_CASE` pour les variables statiques et les constantes ; +- `'lowercase` pour les durées de vie (*lifetimes*). + +Les [Rust API Guidelines] recommandent également des conventions de nommage +plus précises pour certaines constructions particulières : + +- (C-CONV) pour les méthodes de conversion (`as_`, `to_`, `into_`) ; +- (C-GETTER) pour les accesseurs ; +- (C-ITER) pour les méthodes produisant des itérateurs ; +- (C-ITER-TY) pour les types itérateur ; +- (C-FEATURE) pour les noms de *features* ; +- (C-WORD-ORDER) pour la cohérence sur l'ordre des mots. + +> **Règle {{#check LANG-NAMING | Respect des conventions de nommage}}** +> +> Le développement d'une application sécurisée doit suivre les conventions de +> nommage décrites dans les [Rust API Guidelines]. + +[rfc 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md +[rust api guidelines]: https://rust-lang.github.io/api-guidelines/ + diff --git a/src/fr/05_typesystem.md b/src/fr/05_typesystem.md index 58ec8ca8..b4e932fa 100644 --- a/src/fr/05_typesystem.md +++ b/src/fr/05_typesystem.md @@ -7,319 +7,3 @@ des confusions à propos du code qui est vraiment exécuté à la suite d'une résolution de contraintes de traits complexes). --> - -## Traits de la bibliothèque standard - -### Trait `Drop` : le destructeur - -Les types implémentent le trait `std::ops::Drop` dans le but d'effectuer -certaines opérations lorsque la mémoire associée à une valeur est réclamée. -`Drop` est l'équivalent Rust d'un destructeur en C++ ou un finaliseur en Java. - -`Drop` agit récursivement, depuis la valeur externe vers les valeurs imbriquées. -Lorsqu'une valeur sort du scope (ou est explicitement relâchée avec -`std::mem::drop`), elle est relâchée en deux étapes. La première étape a lieu -uniquement si le type de la valeur en question implémente le trait `Drop` et -consiste en l'appel de la méthode `drop`. La seconde étape consiste en la -répétition de processus de *drop* récursivement sur tous les champs que contient -la valeur. Il est à noter que l'implémentation de `Drop` est -*responsable uniquement de la valeur extérieure*. - -Tout d'abord, l'implémentation de `Drop` ne doit pas être systématique. Elle est -nécessaire uniquement lorsque le type requiert un traitement logique à la -destruction. `Drop` est typiquement utilisé dans le cas du relâchement des -ressources externes (connexions réseau, fichier, etc.) ou de ressources mémoire -complexes (*smart pointers* comme les `Box` ou les `Rc` par exemple). Au -final, il est probable que l'implémentation du trait `Drop` contienne des blocs -`unsafe` ainsi que d'autres opérations critiques du point de vue de la sécurité. - -> **Recommandation {{#check LANG-DROP | Justification de l'implémentation du trait `Drop`}}** -> -> Dans un développement sécurisé en Rust, l'implémentation du trait -> `std::ops::Drop` doit être justifiée, documentée et examinée par des pairs. - -Ensuite, le système de types de Rust assure seulement la sûreté mémoire et, -du point de vue du typage, des `drop`s peuvent tout à fait être manqués. -Plusieurs situations peuvent mener à manquer des `drop`s, comme : - -- un cycle dans la référence (par exemple avec `Rc` ou `Arc`) ; -- un appel explicite à `std::mem::forget` (ou `core::mem::forget`) (voir - paragraphe à propos de [`forget` et des fuites de mémoire](05_memory.html#forget-et-fuites-de-mémoire) ; -- un `panic` dans un `drop` ; -- un arrêt du programme (et un `panic` lorsque `abort-on-panic` est activé). - -Les `drop`s manqués peuvent mener à l'exposition de données sensibles ou bien -encore à l'épuisement de ressources limitées et par là même à des problèmes -d'indisponibilité. - -> **Règle {{#check LANG-DROP-NO-PANIC | Absence de `panic` dans l'implémentation de `Drop`}}** -> -> Dans un développement sécurisé en Rust, l'implémentation du trait -> `std::ops::Drop` ne doit pas causer de `panic`. - -En plus des `panic`s, les `drop`s contenant du code critique doivent être -protégés. - -> **Règle {{#check LANG-DROP-NO-CYCLE | Absence de cycles de références avec valeurs `Drop`ables}}** -> -> Les valeurs dont le type implémente `Drop` ne doivent pas être incluses, -> directement ou indirectement, dans un cycle de références à compteurs. - - - -> **Recommandation {{#check LANG-DROP-SEC | Sécurité assurée par d'autres mécanismes en plus du trait `Drop`}}** -> -> Certaines opérations liées à la sécurité d'une application à la fin d'un -> traitement (comme l'effacement de secrets cryptographiques par exemple) ne -> doivent pas reposer uniquement sur l'implémentation du trait `Drop`. - -### Les traits `Send` et `Sync` - -Les traits `Send` et `Sync` (définis dans `std::marker` ou `core::marker`) sont -des marqueurs utilisés pour assurer la sûreté des accès concurrents en Rust. -Lorsqu'ils sont correctement implémentés, ils permettent au compilateur Rust de -garantir l'absence de problèmes d'accès concurrents. Leurs sémantiques sont -définies comme suit : - -- Un type est `Send` s’il est sûr d'envoyer (*move*) des valeurs de ce type vers - un autre fil d'exécution. -- Un type est `Sync` s’il est sûr de partager des valeurs de ce type par une - référence immutable avec un autre fil d'exécution. - -Ces deux traits sont des traits *unsafe*, c'est-à-dire que le compilateur Rust -ne vérifie d'aucune manière que leur implémentation est correcte. Le danger est -réel : une implémentation incorrecte peut mener à un **comportement indéfini**. - -Heureusement, dans la plupart des cas, il n'est pas nécessaire de fournir une -implémentation. En Rust, la quasi-totalité des types primitifs implémente -`Send` et `Sync`, et dans la majorité des cas, Rust fournit de manière -automatique une implémentation pour les types composés. Quelques exceptions -notables sont : - -- les pointeurs `raw`, qui n'implémentent ni `Send`, ni `Sync`, puisqu'ils - n'offrent aucune garantie quant à la sûreté ; -- les références `UnsafeCell`, qui n'implémentent pas `Sync` (et par extensions, - les références `Cell` et `RefCell` non plus), puisqu'elles autorisent la - mutabilité des valeurs contenues (*interior mutability*) ; -- les références `Rc`, qui n'implémentent ni `Send`, ni `Sync`, puisque les - compteurs de références seraient partagés de manière désynchronisée. - -L'implémentation automatique de `Send` (respectivement `Sync`) a lieu pour les -types composés (structures ou énumérations) lorsque tous les champs contenus -implémentent `Send` (respectivement `Sync`). Une fonctionnalité notable, mais -**instable**, de Rust (depuis 1.37.0) permet d'empêcher cette implémentation -automatique en annotant explicitement le type considéré avec une -_négation d'implementation_ : - -```rust,ignore,noplaypen -#![feature(option_builtin_traits)] - -struct SpecialType(u8); -impl !Send for SpecialType {} -impl !Sync for SpecialType {} -``` -L'implémentation négative de `Send` ou `Sync` est également utilisée dans la -bibliothèque standard pour les exceptions, et est automatiquement implémentée -lorsque cela est approprié. En résultat, la documentation générée est toujours -explicite : un type implémente soit `Send` (respectivement `Sync`), soit -`!Send` (respectivement `!Sync`). - -En guise d'alternative *stable* à l'implémentation négative, il est possible -d'utiliser un champ typé par un type fantôme (`PhantomData`) : - -```rust,noplaypen -# use std::marker::PhantomData; -# -struct SpecialType(u8, PhantomData<*const ()>); -``` - -> **Recommandation {{#check LANG-SYNC-TRAITS | Justification de l'implémentation des traits `Send` et `Sync`}}** -> -> Dans un développement sécurisé en Rust, l'implémentation manuelle des traits -> `Send` et `Sync` doit être évitée, et, si nécessaire, doit être justifiée, -> documentée et révisée par des pairs. - -### Les traits de comparaison : `PartialEq`, `Eq`, `PartialOrd`, `Ord` - -Les comparaisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) en Rust reposent sur quatre -traits de la bibliothèque standard disponibles dans `std::cmp` (ou `core::cmp` -pour une compilation avec `no_std`) : - -- `PartialEq` qui définit la relation d'équivalence partielle entre objets - de types `Self` et `Rhs` ; -- `PartialOrd` qui définit la relation d'ordre partiel entre les objets de - types `Self` et `Rhs` ; -- `Eq` qui définit la relation d'équivalence totale entre les objets du même - type. Il s'agit d'un trait de marquage qui requiert le trait - `PartialEq` ; -- `Ord` qui définit la relation d'ordre total entre les objets du même type. - Le trait `PartialOrd` est alors requis. - -Comme stipulé dans la documentation de la bibliothèque standard, Rust présuppose -**de nombreux invariants** lors de l'implémentation de ces traits : - -- Pour `PartialEq` : - - - *Cohérence interne* : `a.ne(b)` est équivalent à `!a.eq(b)`, c.-à-d., `ne` - est le strict inverse de `eq`. Cela correspond précisément à - l'implémentation par défaut de `ne`. - - - *Symétrie* : `a.eq(b)` et `b.eq(a)` sont équivalents. Du point de vue du - développeur, cela signifie que : - - - `PartialEq` est implémenté pour le type `A` (noté `A: PartialEq`). - - `PartialEq` est implémenté pour le type `B` (noté `B: PartialEq`). - - Les deux implémentations sont cohérentes l'une avec l'autre. - - - *Transitivité* : `a.eq(b)` et `b.eq(c)` impliquent `a.eq(c)`. Cela signifie - que : - - - `A: PartialEq`. - - `B: PartialEq`. - - `A: PartialEq`. - - Les trois implémentations sont cohérentes les unes avec les autres (ainsi - qu'avec leurs implémentations symétriques). - -- Pour `Eq` : - - - `PartialEq` est implémenté. - - - *Réflexivité* : `a.eq(a)`. Cela signifie que `PartialEq` est - implémenté (`Eq` ne fournit aucune méthode). - -- Pour `PartialOrd` : - - - *Consistance de la relation d'égalité* : `a.eq(b)` est équivalent à - `a.partial_cmp(b) == Some(std::ordering::Eq)`. - - - *Consistence interne* : - - - `a.lt(b)` ssi `a.partial_cmp(b) == Some(std::ordering::Less)`. - - `a.gt(b)` ssi `a.partial_cmp(b) == Some(std::ordering::Greater)`. - - `a.le(b)` ssi `a.lt(b) || a.eq(b)`. - - `a.ge(b)` ssi `a.gt(b) || a.eq(b)`. - - Il faut noter qu'en définissant seulement `partial_cmp`, la consistance - interne est garantie par les implémentations par défaut de `lt`, `le`, `gt`, - and `ge`. - - - *Antisymétrie* : `a.lt(b)` (respectivement `a.gt(b)`) implique `b.gt(a)` - (respectivement `b.lt(b)`). Du point de vue du développeur, cela signifie - que : - - - `A: PartialOrd`. - - `B: PartialOrd`. - - Les deux implémentations sont cohérentes l'une avec l'autre. - - - *Transitivité* : `a.lt(b)` et `b.lt(c)` impliquent `a.lt(c)` (également avec - `gt`, `le` et `ge`). Cela signifie que : - - - `A: PartialOrd`. - - `B: PartialOrd`. - - `A: PartialOrd`. - - Les trois implémentations sont cohérentes les unes avec les autres (et - avec leurs implémentations symétriques). - -- Pour `Ord` : - - - `PartialOrd` - - - *Totalité* : `a.partial_cmp(b) != None` est toujours vrai. En d'autres mots, - exactement une assertion parmi `a.eq(b)`, `a.lt(b)` et `a.gt(b)` est vraie. - - - *Cohérence avec `PartialOrd`*: `Some(a.cmp(b)) == a.partial_cmp(b)`. - -Le compilateur ne vérifie aucun de ces prérequis, à l'exception des -vérifications sur les types. Toutefois, les comparaisons sont des éléments -importants puisqu'elles jouent un rôle tant dans les propriétés de vivacité -des systèmes critiques comme des ordonnanceurs ou des répartiteurs de charge -que dans les algorithmes optimisés qui peuvent éventuellement utiliser des -blocs `unsafe`. Dans le premier cas d'usage, une mauvaise relation d'ordre -peut causer des problèmes de disponibilité comme des interblocages. Dans le -second cas, cela peut mener à des problèmes classiques de sécurité liés à des -violations de propriétés de sûreté mémoire. C'est là encore un atout que de -limiter au possible l'utilisation des blocs `unsafe`. - -> **Règle {{#check LANG-CMP-INV | Respect des invariants des traits de comparaison standards}}** -> -> Dans un développement sécurisé en Rust, l'implémentation des traits de -> comparaison standards doit respecter les invariants décrits dans la -> documentation. - -> **Recommandation {{#check LANG-CMP-DEFAULTS | Utilisation des implémentations par défaut des traits de comparaison standards}}** -> -> Dans un développement sécurisé en Rust, l'implémentation des traits de -> comparaison standard ne doit être effectuée que par l'implémentation des -> méthodes ne fournissant pas d'implémentation par défaut, dans le but de -> réduire le risque de violation des invariants associés auxdits traits. - -Il existe un *lint* Clippy qui permet de vérifier que `PartialEq::ne` n'est pas -défini lors d'une implémentation du trait `PartialEq`. - -Rust propose une façon de fournir automatiquement des implémentations par défaut -pour les traits de comparaison, au travers de l'attribut `#[derive(...)]` : - -- La dérivation de `PartialEq` implémente `PartialEq` avec une **égalité - structurelle** à condition que chacun des types des données membres implémente - `PartialEq`. -- La dérivation de `Eq` implémente le trait de marquage `Eq` à condition que - chacun des types des données membres implémente `Eq`. -- La dérivation de `PartialOrd` implémente `PartialOrd` comme un **ordre - lexicographique** à condition que chacun des types des données membres - implémente `PartialOrd`. -- La dérivation de `Ord` implémente `Ord` comme un **ordre lexicographique** à - condition que chacun des types des données membres implémente `Ord`. - -Par exemple, le court extrait de code suivant montre comment comparer deux -valeurs de type `T1` facilement. Toutes les assertions sont vraies. - -```rust -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct T1 { - a: u8, b: u8 -} - -# fn main() { -assert!(&T1 { a: 0, b: 0 } == Box::new(T1 { a: 0, b: 0 }).as_ref()); -assert!(T1 { a: 1, b: 0 } > T1 { a: 0, b: 0 }); -assert!(T1 { a: 1, b: 1 } > T1 { a: 1, b: 0 }); -# println!("tous les tests sont validés."); -# } -``` - -> **Attention** -> -> La dérivation des traits de comparaison pour les types composites dépend de -> **l'ordre de déclaration des champs** et non de leur nom. -> -> D'abord, cela implique que changer l'ordre des champs modifie l'ordre des -> valeurs. Par exemple, en considérant le type suivant : -> -> ```rust,noplaypen -> #[derive(PartialEq, Eq, PartialOrd, Ord)] -> struct T2{ -> b: u8, a: u8 -> }; -> ``` -> -> on a `T1 {a: 1, b: 0} > T1 {a: 0, b: 1}` mais -> `T2 {a: 1, b: 0} < T2 {a: 0, b: 1}`. -> -> Ensuite, si une comparaison sous-jacente provoque un `panic`, l'ordre peut -> changer le résultat à cause de l'utilisation d'un opérateur logique court- -> circuitant dans l'implémentation automatique. -> -> Pour les énumérations, les comparaisons dérivées dépendent d'abord de -> **l'ordre des variants**, puis de l'ordre des champs. - -En dépit de ces avertissements sur les ordres dérivés, les comparaisons dérivées -automatiquement sont bien moins sujettes à erreurs que des implémentations -manuelles, et rendent le code plus court et plus simple à maintenir. - -> **Recommandation {{#check LANG-CMP-DERIVE | Dérivation des traits de comparaison lorsque c'est possible}}** -> -> Dans un développement sécurisé en Rust, l'implémentation des traits de -> comparaison standard doit être automatiquement dérivée à l'aide de -> `#[derive(...)]` lorsque l'égalité structurelle et l'ordre lexicographique -> sont nécessaires. Toute implémentation manuelle d'un trait de comparaison -> standard doit être justifiée et documentée. diff --git a/src/fr/SUMMARY.md b/src/fr/SUMMARY.md index 527617cc..5eb577a3 100644 --- a/src/fr/SUMMARY.md +++ b/src/fr/SUMMARY.md @@ -1,15 +1,30 @@ # Summary -- [Introduction](01_introduction.md) +[Introduction](01_introduction.md) + +# Cycle de vie + - [Environnement de développement](02_devenv.md) - [Bibliothèques](03_libraries.md) -- [Généralités sur le langage](04_language.md) -- [Système de types](05_typesystem.md) + +# Langage + +- [Garanties du langage](05_guarantees.md) +- [Nommage](05_naming.md) + +- [Gestion des entiers](05_integer.md) +- [Gestion des erreurs](05_errors.md) + +- [Traits centraux](05_central_traits.md) - [Unsafe Rust](06_unsafe.md) - [Généralités](06_1_unsafe_generalities.md) - [Gestion de la mémoire](06_2_unsafe_memory.md) - [FFI](06_3_unsafe_ffi.md) +# Écosystème + +- [Bibliothèque standard](04_standard.md) + [License](LICENSE.md) From 9daa41e8f2c7d9bff89634c040f48507e1d89142 Mon Sep 17 00:00:00 2001 From: hg-anssi Date: Tue, 22 Jul 2025 12:01:21 +0200 Subject: [PATCH 2/4] remove files numbering --- src/en/SUMMARY.md | 32 +++++++++---------- ...05_central_traits.md => central_traits.md} | 0 src/en/{02_devenv.md => devenv.md} | 0 src/en/{05_errors.md => errors.md} | 0 src/en/{05_guarantees.md => guarantees.md} | 0 src/en/{05_integer.md => integer.md} | 0 .../{01_introduction.md => introduction.md} | 0 src/en/{03_libraries.md => libraries.md} | 0 src/en/{05_macros.md => macros.md} | 0 src/en/{05_naming.md => naming.md} | 0 src/en/{04_standard.md => standard.md} | 0 src/en/{08_testfuzz.md => testfuzz.md} | 0 src/en/{05_typesystem.md => typesystem.md} | 0 src/en/{06_unsafe.md => unsafe.md} | 0 src/en/{06_3_unsafe_ffi.md => unsafe_ffi.md} | 0 ...generalities.md => unsafe_generalities.md} | 0 ...06_2_unsafe_memory.md => unsafe_memory.md} | 0 src/fr/SUMMARY.md | 32 +++++++++---------- ...05_central_traits.md => central_traits.md} | 0 src/fr/{02_devenv.md => devenv.md} | 0 src/fr/{05_errors.md => errors.md} | 0 src/fr/{05_guarantees.md => guarantees.md} | 0 src/fr/{05_integer.md => integer.md} | 0 .../{01_introduction.md => introduction.md} | 0 src/fr/{03_libraries.md => libraries.md} | 0 src/fr/{05_macros.md => macros.md} | 0 src/fr/{05_naming.md => naming.md} | 0 src/fr/{04_standard.md => standard.md} | 0 src/fr/{08_testfuzz.md => testfuzz.md} | 0 src/fr/{05_typesystem.md => typesystem.md} | 0 src/fr/{06_unsafe.md => unsafe.md} | 0 src/fr/{06_3_unsafe_ffi.md => unsafe_ffi.md} | 0 ...generalities.md => unsafe_generalities.md} | 0 ...06_2_unsafe_memory.md => unsafe_memory.md} | 0 34 files changed, 32 insertions(+), 32 deletions(-) rename src/en/{05_central_traits.md => central_traits.md} (100%) rename src/en/{02_devenv.md => devenv.md} (100%) rename src/en/{05_errors.md => errors.md} (100%) rename src/en/{05_guarantees.md => guarantees.md} (100%) rename src/en/{05_integer.md => integer.md} (100%) rename src/en/{01_introduction.md => introduction.md} (100%) rename src/en/{03_libraries.md => libraries.md} (100%) rename src/en/{05_macros.md => macros.md} (100%) rename src/en/{05_naming.md => naming.md} (100%) rename src/en/{04_standard.md => standard.md} (100%) rename src/en/{08_testfuzz.md => testfuzz.md} (100%) rename src/en/{05_typesystem.md => typesystem.md} (100%) rename src/en/{06_unsafe.md => unsafe.md} (100%) rename src/en/{06_3_unsafe_ffi.md => unsafe_ffi.md} (100%) rename src/en/{06_1_unsafe_generalities.md => unsafe_generalities.md} (100%) rename src/en/{06_2_unsafe_memory.md => unsafe_memory.md} (100%) rename src/fr/{05_central_traits.md => central_traits.md} (100%) rename src/fr/{02_devenv.md => devenv.md} (100%) rename src/fr/{05_errors.md => errors.md} (100%) rename src/fr/{05_guarantees.md => guarantees.md} (100%) rename src/fr/{05_integer.md => integer.md} (100%) rename src/fr/{01_introduction.md => introduction.md} (100%) rename src/fr/{03_libraries.md => libraries.md} (100%) rename src/fr/{05_macros.md => macros.md} (100%) rename src/fr/{05_naming.md => naming.md} (100%) rename src/fr/{04_standard.md => standard.md} (100%) rename src/fr/{08_testfuzz.md => testfuzz.md} (100%) rename src/fr/{05_typesystem.md => typesystem.md} (100%) rename src/fr/{06_unsafe.md => unsafe.md} (100%) rename src/fr/{06_3_unsafe_ffi.md => unsafe_ffi.md} (100%) rename src/fr/{06_1_unsafe_generalities.md => unsafe_generalities.md} (100%) rename src/fr/{06_2_unsafe_memory.md => unsafe_memory.md} (100%) diff --git a/src/en/SUMMARY.md b/src/en/SUMMARY.md index 7d67f57c..05f2a8ad 100644 --- a/src/en/SUMMARY.md +++ b/src/en/SUMMARY.md @@ -1,30 +1,30 @@ # Summary -[Introduction](01_introduction.md) +[Introduction](introduction.md) # Lifecycle -- [Development environment](02_devenv.md) -- [Libraries](03_libraries.md) +- [Development environment](devenv.md) +- [Libraries](libraries.md) # Language -- [Language guarantees](05_guarantees.md) -- [Naming](05_naming.md) - -- [Integer operations](05_integer.md) -- [Error handling](05_errors.md) - -- [Central traits](05_central_traits.md) -- [Unsafe Rust](06_unsafe.md) - - [Generalities](06_1_unsafe_generalities.md) - - [Memory management](06_2_unsafe_memory.md) - - [Foreign Function Interface](06_3_unsafe_ffi.md) +- [Language guarantees](guarantees.md) +- [Naming](naming.md) + +- [Integer operations](integer.md) +- [Error handling](errors.md) + +- [Central traits](central_traits.md) +- [Unsafe Rust](unsafe.md) + - [Generalities](unsafe_generalities.md) + - [Memory management](unsafe_memory.md) + - [Foreign Function Interface](unsafe_ffi.md) # Ecosystem -- [Standard library](04_standard.md) +- [Standard library](standard.md) [Licence](LICENCE.md) - + diff --git a/src/en/05_central_traits.md b/src/en/central_traits.md similarity index 100% rename from src/en/05_central_traits.md rename to src/en/central_traits.md diff --git a/src/en/02_devenv.md b/src/en/devenv.md similarity index 100% rename from src/en/02_devenv.md rename to src/en/devenv.md diff --git a/src/en/05_errors.md b/src/en/errors.md similarity index 100% rename from src/en/05_errors.md rename to src/en/errors.md diff --git a/src/en/05_guarantees.md b/src/en/guarantees.md similarity index 100% rename from src/en/05_guarantees.md rename to src/en/guarantees.md diff --git a/src/en/05_integer.md b/src/en/integer.md similarity index 100% rename from src/en/05_integer.md rename to src/en/integer.md diff --git a/src/en/01_introduction.md b/src/en/introduction.md similarity index 100% rename from src/en/01_introduction.md rename to src/en/introduction.md diff --git a/src/en/03_libraries.md b/src/en/libraries.md similarity index 100% rename from src/en/03_libraries.md rename to src/en/libraries.md diff --git a/src/en/05_macros.md b/src/en/macros.md similarity index 100% rename from src/en/05_macros.md rename to src/en/macros.md diff --git a/src/en/05_naming.md b/src/en/naming.md similarity index 100% rename from src/en/05_naming.md rename to src/en/naming.md diff --git a/src/en/04_standard.md b/src/en/standard.md similarity index 100% rename from src/en/04_standard.md rename to src/en/standard.md diff --git a/src/en/08_testfuzz.md b/src/en/testfuzz.md similarity index 100% rename from src/en/08_testfuzz.md rename to src/en/testfuzz.md diff --git a/src/en/05_typesystem.md b/src/en/typesystem.md similarity index 100% rename from src/en/05_typesystem.md rename to src/en/typesystem.md diff --git a/src/en/06_unsafe.md b/src/en/unsafe.md similarity index 100% rename from src/en/06_unsafe.md rename to src/en/unsafe.md diff --git a/src/en/06_3_unsafe_ffi.md b/src/en/unsafe_ffi.md similarity index 100% rename from src/en/06_3_unsafe_ffi.md rename to src/en/unsafe_ffi.md diff --git a/src/en/06_1_unsafe_generalities.md b/src/en/unsafe_generalities.md similarity index 100% rename from src/en/06_1_unsafe_generalities.md rename to src/en/unsafe_generalities.md diff --git a/src/en/06_2_unsafe_memory.md b/src/en/unsafe_memory.md similarity index 100% rename from src/en/06_2_unsafe_memory.md rename to src/en/unsafe_memory.md diff --git a/src/fr/SUMMARY.md b/src/fr/SUMMARY.md index 5eb577a3..e7bd4dcb 100644 --- a/src/fr/SUMMARY.md +++ b/src/fr/SUMMARY.md @@ -1,30 +1,30 @@ # Summary -[Introduction](01_introduction.md) +[Introduction](introduction.md) # Cycle de vie -- [Environnement de développement](02_devenv.md) -- [Bibliothèques](03_libraries.md) +- [Environnement de développement](devenv.md) +- [Bibliothèques](libraries.md) # Langage -- [Garanties du langage](05_guarantees.md) -- [Nommage](05_naming.md) - -- [Gestion des entiers](05_integer.md) -- [Gestion des erreurs](05_errors.md) - -- [Traits centraux](05_central_traits.md) -- [Unsafe Rust](06_unsafe.md) - - [Généralités](06_1_unsafe_generalities.md) - - [Gestion de la mémoire](06_2_unsafe_memory.md) - - [FFI](06_3_unsafe_ffi.md) +- [Garanties du langage](guarantees.md) +- [Nommage](naming.md) + +- [Gestion des entiers](integer.md) +- [Gestion des erreurs](errors.md) + +- [Traits centraux](central_traits.md) +- [Unsafe Rust](unsafe.md) + - [Généralités](unsafe_generalities.md) + - [Gestion de la mémoire](unsafe_memory.md) + - [FFI](unsafe_ffi.md) # Écosystème -- [Bibliothèque standard](04_standard.md) +- [Bibliothèque standard](standard.md) [License](LICENSE.md) - + diff --git a/src/fr/05_central_traits.md b/src/fr/central_traits.md similarity index 100% rename from src/fr/05_central_traits.md rename to src/fr/central_traits.md diff --git a/src/fr/02_devenv.md b/src/fr/devenv.md similarity index 100% rename from src/fr/02_devenv.md rename to src/fr/devenv.md diff --git a/src/fr/05_errors.md b/src/fr/errors.md similarity index 100% rename from src/fr/05_errors.md rename to src/fr/errors.md diff --git a/src/fr/05_guarantees.md b/src/fr/guarantees.md similarity index 100% rename from src/fr/05_guarantees.md rename to src/fr/guarantees.md diff --git a/src/fr/05_integer.md b/src/fr/integer.md similarity index 100% rename from src/fr/05_integer.md rename to src/fr/integer.md diff --git a/src/fr/01_introduction.md b/src/fr/introduction.md similarity index 100% rename from src/fr/01_introduction.md rename to src/fr/introduction.md diff --git a/src/fr/03_libraries.md b/src/fr/libraries.md similarity index 100% rename from src/fr/03_libraries.md rename to src/fr/libraries.md diff --git a/src/fr/05_macros.md b/src/fr/macros.md similarity index 100% rename from src/fr/05_macros.md rename to src/fr/macros.md diff --git a/src/fr/05_naming.md b/src/fr/naming.md similarity index 100% rename from src/fr/05_naming.md rename to src/fr/naming.md diff --git a/src/fr/04_standard.md b/src/fr/standard.md similarity index 100% rename from src/fr/04_standard.md rename to src/fr/standard.md diff --git a/src/fr/08_testfuzz.md b/src/fr/testfuzz.md similarity index 100% rename from src/fr/08_testfuzz.md rename to src/fr/testfuzz.md diff --git a/src/fr/05_typesystem.md b/src/fr/typesystem.md similarity index 100% rename from src/fr/05_typesystem.md rename to src/fr/typesystem.md diff --git a/src/fr/06_unsafe.md b/src/fr/unsafe.md similarity index 100% rename from src/fr/06_unsafe.md rename to src/fr/unsafe.md diff --git a/src/fr/06_3_unsafe_ffi.md b/src/fr/unsafe_ffi.md similarity index 100% rename from src/fr/06_3_unsafe_ffi.md rename to src/fr/unsafe_ffi.md diff --git a/src/fr/06_1_unsafe_generalities.md b/src/fr/unsafe_generalities.md similarity index 100% rename from src/fr/06_1_unsafe_generalities.md rename to src/fr/unsafe_generalities.md diff --git a/src/fr/06_2_unsafe_memory.md b/src/fr/unsafe_memory.md similarity index 100% rename from src/fr/06_2_unsafe_memory.md rename to src/fr/unsafe_memory.md From 49d5ac4f59ce748bfa6e5595227f849903f5935d Mon Sep 17 00:00:00 2001 From: hg-anssi Date: Mon, 1 Sep 2025 17:07:53 +0200 Subject: [PATCH 3/4] reorganize files in directories --- src/en/SUMMARY.md | 6 +++--- src/en/{unsafe_ffi.md => unsafe/ffi.md} | 0 src/en/{unsafe_generalities.md => unsafe/generalities.md} | 0 src/en/{unsafe_memory.md => unsafe/memory.md} | 0 src/fr/SUMMARY.md | 6 +++--- src/fr/{unsafe_ffi.md => unsafe/ffi.md} | 0 src/fr/{unsafe_generalities.md => unsafe/generalities.md} | 0 src/fr/{unsafe_memory.md => unsafe/memory.md} | 0 8 files changed, 6 insertions(+), 6 deletions(-) rename src/en/{unsafe_ffi.md => unsafe/ffi.md} (100%) rename src/en/{unsafe_generalities.md => unsafe/generalities.md} (100%) rename src/en/{unsafe_memory.md => unsafe/memory.md} (100%) rename src/fr/{unsafe_ffi.md => unsafe/ffi.md} (100%) rename src/fr/{unsafe_generalities.md => unsafe/generalities.md} (100%) rename src/fr/{unsafe_memory.md => unsafe/memory.md} (100%) diff --git a/src/en/SUMMARY.md b/src/en/SUMMARY.md index 05f2a8ad..83ce4126 100644 --- a/src/en/SUMMARY.md +++ b/src/en/SUMMARY.md @@ -17,9 +17,9 @@ - [Central traits](central_traits.md) - [Unsafe Rust](unsafe.md) - - [Generalities](unsafe_generalities.md) - - [Memory management](unsafe_memory.md) - - [Foreign Function Interface](unsafe_ffi.md) + - [Generalities](unsafe/generalities.md) + - [Memory management](unsafe/memory.md) + - [Foreign Function Interface](unsafe/ffi.md) # Ecosystem diff --git a/src/en/unsafe_ffi.md b/src/en/unsafe/ffi.md similarity index 100% rename from src/en/unsafe_ffi.md rename to src/en/unsafe/ffi.md diff --git a/src/en/unsafe_generalities.md b/src/en/unsafe/generalities.md similarity index 100% rename from src/en/unsafe_generalities.md rename to src/en/unsafe/generalities.md diff --git a/src/en/unsafe_memory.md b/src/en/unsafe/memory.md similarity index 100% rename from src/en/unsafe_memory.md rename to src/en/unsafe/memory.md diff --git a/src/fr/SUMMARY.md b/src/fr/SUMMARY.md index e7bd4dcb..6e717a08 100644 --- a/src/fr/SUMMARY.md +++ b/src/fr/SUMMARY.md @@ -17,9 +17,9 @@ - [Traits centraux](central_traits.md) - [Unsafe Rust](unsafe.md) - - [Généralités](unsafe_generalities.md) - - [Gestion de la mémoire](unsafe_memory.md) - - [FFI](unsafe_ffi.md) + - [Généralités](unsafe/generalities.md) + - [Gestion de la mémoire](unsafe/memory.md) + - [FFI](unsafe/ffi.md) # Écosystème diff --git a/src/fr/unsafe_ffi.md b/src/fr/unsafe/ffi.md similarity index 100% rename from src/fr/unsafe_ffi.md rename to src/fr/unsafe/ffi.md diff --git a/src/fr/unsafe_generalities.md b/src/fr/unsafe/generalities.md similarity index 100% rename from src/fr/unsafe_generalities.md rename to src/fr/unsafe/generalities.md diff --git a/src/fr/unsafe_memory.md b/src/fr/unsafe/memory.md similarity index 100% rename from src/fr/unsafe_memory.md rename to src/fr/unsafe/memory.md From c5b67e105af86ac2c13fcfcf72560c141fd826ee Mon Sep 17 00:00:00 2001 From: hg-anssi Date: Mon, 1 Sep 2025 17:15:30 +0200 Subject: [PATCH 4/4] =?UTF-8?q?effacement=20s=C3=A9curis=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/en/SUMMARY.md | 1 + src/en/erasure.md | 32 ++++++++++ src/fr/SUMMARY.md | 2 + src/fr/erasure.md | 150 ++++++++++++++++++++++++++++++++++++++++++++ src/fr/semantics.md | 27 ++++++++ 5 files changed, 212 insertions(+) create mode 100644 src/en/erasure.md create mode 100644 src/fr/erasure.md create mode 100644 src/fr/semantics.md diff --git a/src/en/SUMMARY.md b/src/en/SUMMARY.md index 83ce4126..f4fc8fa3 100644 --- a/src/en/SUMMARY.md +++ b/src/en/SUMMARY.md @@ -13,6 +13,7 @@ - [Naming](naming.md) - [Integer operations](integer.md) +- [Secure erasure](erasure.md) - [Error handling](errors.md) - [Central traits](central_traits.md) diff --git a/src/en/erasure.md b/src/en/erasure.md new file mode 100644 index 00000000..91729c3e --- /dev/null +++ b/src/en/erasure.md @@ -0,0 +1,32 @@ +# Secure erasure + +Zeroing memory is useful for sensitive variables, especially if the +Rust code is used through FFI. + +> **Rule {{#check MEM-ZERO | Zero out memory of sensitive data after use}}** +> +> Variables containing sensitive data must be zeroed out after use, using +> functions that will not be removed by the compiler optimizations, like +> `std::ptr::write_volatile` or the `zeroize` crate. + +The following code shows how to define an integer type that will be set to +0 when freed, using the `Drop` trait: + +```rust +/// Example: u32 newtype, set to 0 when freed +pub struct ZU32(pub u32); + +impl Drop for ZU32 { + fn drop(&mut self) { + println!("zeroing memory"); + unsafe{ ::std::ptr::write_volatile(&mut self.0, 0) }; + } +} + +# fn main() { +{ + let i = ZU32(42); + // ... +} // i is freed here +# } +``` \ No newline at end of file diff --git a/src/fr/SUMMARY.md b/src/fr/SUMMARY.md index 6e717a08..ab240986 100644 --- a/src/fr/SUMMARY.md +++ b/src/fr/SUMMARY.md @@ -9,10 +9,12 @@ # Langage +- [Sémantique](semantics.md) - [Garanties du langage](guarantees.md) - [Nommage](naming.md) - [Gestion des entiers](integer.md) +- [Effacement sécurisé](erasure.md) - [Gestion des erreurs](errors.md) - [Traits centraux](central_traits.md) diff --git a/src/fr/erasure.md b/src/fr/erasure.md new file mode 100644 index 00000000..3992c14b --- /dev/null +++ b/src/fr/erasure.md @@ -0,0 +1,150 @@ +# Effacement sécurisé + +L'effacement sécurisé (mise à zéro) est nécessaire pour les variables sensibles, +en particulier dans lorsque le code Rust est utilisé *via* des FFI. + +> **Règle {{#check MEM-ZERO | Mise à zéro des données sensibles après utilisation}}** +> +> Les variables contenant des données sensibles doivent être mises à zéro après +> utilisation. + + + +
+ +Les opérations d'effacement de la mémoire son compliquées à réaliser en général, et +avec Rust en particulier. En effet, comme en C, la sécurité de la mémoire n'est pas +un comportement *observable*, c'est à dire dont le compilateur se porte garant. + +Du fait des nombreuses optimisations[^note] du compilateur, +les recommendations suivantes ne garantissent donc pas l'effacement complet des secrets +après leur utilisation, mais sont données en *best-effort*. + +
+ +[^note]: ... et éventuellement de celles futures ! + + + +## Forcer l'effacement + +Une première difficulté naît des optimisations du compilateur : en général, il considère inutiles des opérations +d'écriture en mémoire sans lecture subséquente et peut les supprimer. + +Par exemple, dans le code suivant, l'écriture en mémoire suivante n'est pas effectuée + +```rust +pub fn main() { + let mut n: u64 = 0x4953534e41; + println!("{}", n); + n = 0; // optimized away! +} +``` + +Il existe dans la bibliothèque standard Rust des fonctions `unsafe` forçant l'écriture en mémoire malgré les optimisations +du compilateur. Par exemple, la fonction `std::ptr::write_volatile` ne sera jamais supprimée +par le compilateur. Par exemple, la fonction suivante appliquera toujours l'effacement de la mémoire d'un entier. + +```rust +fn erase(mut n: u8) { + println!("zeroing memory"); + unsafe { ::std::ptr::write_volatile(&mut n, 0) }; +} +``` + +Cependant, cette fonction étant `unsafe`, on lui préférera si possible la crate `zeroize`. + +> **Règle {{#check MEM-ZERO-EFFECTIVE | Effectivité de l'effacement de la mémoire}}** +> +> Les développements concernant la mise à zéro de variables sensibles +> s'assureront de l'exécution effective de l'opération. + + + + + + +## Déplacements de valeurs sensibles + +La [sémantique par déplacement](semantics.md#déplacement-des-valeurs) de Rust induit une contrainte +supplémentaire dans le contrôle des variables sensibles. Le déplacement est une simple copie +bit à bit d'un emplacement à un autre, sans action paramétrable par l'utilisateur (excluant par exemple +d'éventuel recours à `drop`). + +De plus, le déplacement étant un détail d'implémentation du compilateur il est difficile de +savoir à quel moment il interviendra. + +> **Règle {{#check MEM-ZERO-NOMOVE | Absence de déplacements de valeurs sensibles}}** +> +> Les valeurs sensibles ne devront pas être déplacées après leur création. + +Il s'en suit la recommendation suivante découlant directement de la précédente + +> **Règle {{#check MEM-ZERO-BYREF | Absence de transfert de propriété d'une valeur sensible}}** +> +> La propriété d'un secret n'est pas transferable. Une fois le secret créé (même vide), les seules façons +> permettant d'accéder à un secret sont +> +> * la copie (trait `Copy`) ou le clonage (trait `Clone`) uniquement si le type du secret implémente `Drop` dans laquelle le secret est effacé, +> * la référence (`&T` ou `&mut T`) + +
+ +Il est à noter que ces précautions ne garantissent pas le bonne effacement d'un secret : en effet, +il est possible dans certaines situations que le compilateur ajoute des optimisations cassant les invariants +mis en place pour empêcher le déplacement d'un secret. + +Par exemple, il peut choisir de transformer un passage par référence en passage par valeur, ce +qui met à mal la précaution [MEM-ZERO-BYREF](#MEM-ZERO-BYREF). + +
+ + + +> **Règle {{#check MEM-ZERO-WRAPPED-INIT | Création de valeurs sensible dans un environnement sécurisé}}** +> +> Les valeurs sensibles seront initialisées dans un environnement s'assurant que ces valeurs seront bien +> effacées après leur usage. + +L'initialisation suivante est **mauvaise** car la valeur du secret est d'abord créée dans un environnement où l'effacement n'est pas assuré + +```rust +struct Secret {...} + +impl Secret { + /// This initialisation function is no secure since the content + /// of the secret is first generated outside the secure wrapper [`Secret`] + fn new(content : [u8; 16]) +} + +impl Drop for Secret { + fn drop(&self) { + // securely erase the content of a [`Secret`] + ... + } +} +``` + \ No newline at end of file diff --git a/src/fr/semantics.md b/src/fr/semantics.md new file mode 100644 index 00000000..47fd1318 --- /dev/null +++ b/src/fr/semantics.md @@ -0,0 +1,27 @@ +# Sémantique + +Les choix sémantiques de Rust, motivés en partie pour des besoins de sûreté, +doivent être bien compris pour savoir + +* s'appuyer dessus pour sécuriser son code +* ne pas introduire d'effet indésirables + +## Déplacement des valeurs + +Par défaut, les valeurs de Rust sont considérées comme *déplaçable* à loisir en mémoire. +On donne dans la suite quelques exemples de déplacements en mémoire. + +* Le passage *par valeur* lors d'un appel de fonction peut déplacer en mémoire l'objet. + Par exemple, la fonction `Box::new(t : T) -> Box` *déplace* la valeur `t` + depuis la pile vers le tas. +* Lors du redimensionnement d'un tableau dynamique de type `Vec`, les éléments du + tableau sont tous déplacés depuis l'ancien vers le nouveau tableau. +* Lors du déréférencement d'une variable (par exemple `let y = *x;` où `x` est de type `Box`), + le contenu du tas est déplacé vers la pile. + +Les déplacements sont de simples copies bit à bit depuis l'emplacement initial vers l'emplacement +final, suivi d'une libération mémoire simple (sans `drop`) lors que l'emplacement initial se trouve +sur le tas. + + +Cette particularité complexifie notamment les [effacements sécurisés](erasure.md#fixer-les-valeurs-en-mémoire) de variables sensibles. \ No newline at end of file