diff --git a/src/en/01_introduction.md b/src/en/01_introduction.md index 026d61e0..9b9b46b6 100644 --- a/src/en/01_introduction.md +++ b/src/en/01_introduction.md @@ -69,7 +69,7 @@ Rust ecosystem for secure development. A following chapter focuses on precautions to take when choosing and using external libraries. Then, recommendations about the Rust language constructs are exposed. - +Finally, we introduce advices for writing +tests for a project in Rust. A summary of recommendations presented throughout the document is listed at the end of this guide. diff --git a/src/en/02_devenv.md b/src/en/02_devenv.md index f81f028d..014c5cc1 100644 --- a/src/en/02_devenv.md +++ b/src/en/02_devenv.md @@ -254,4 +254,4 @@ or change the program semantics in some case. There exist other useful tools or `cargo` subcommands for enforcing program security whether by searching for specific code patterns or by providing convenient commands for testing or fuzzing. They are discussed in the following -chapters, according to their goals. +chapters, according to their goals. \ No newline at end of file diff --git a/src/en/04_language.md b/src/en/04_language.md index c265d03b..fcc0c776 100644 --- a/src/en/04_language.md +++ b/src/en/04_language.md @@ -67,6 +67,19 @@ manipulations of memory pointers, the language provides the `unsafe` keyword. > the crate root (typically `main.rs` or `lib.rs`) to generate compilation > errors if `unsafe` is used in the code base. + > ### Information +> +>You can also obtain the same result by adding one of the two blocks below to the `Cargo.toml` file. + +```toml +[lints.rust] +unsafe_code="forbid" +``` + +```toml +[lints.clippy] +unsafe_code = "forbid" +``` ## Integer overflows Although some verification is performed by Rust regarding potential integer @@ -114,25 +127,23 @@ else { println!("{}", res); } > 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. +### Custom Error type implementation + > **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. +To ensure that the above recommendation is implemented correctly, you may check +the [test implementing trait](08_testfuzz.md#implementing-a-trait) section of this guide. + +### Third-party library use Third-party crates may be used to facilitate error handling. Most of them (notably [failure], [snafu], [thiserror]) address the creation of new custom diff --git a/src/en/08_testfuzz.md b/src/en/08_testfuzz.md index 0f20fced..eeb6080e 100644 --- a/src/en/08_testfuzz.md +++ b/src/en/08_testfuzz.md @@ -2,10 +2,126 @@ ## Writing tests -TODO: good practices in writing tests. +Rust offers two types of test built in by default: internal tests and integration tests. +In this section, we will discuss these two types of test as well as a rather special type of test, which is the trait implementation test. -## Fuzzing +> Recommendation {{#check TEST-DRIVEN-DEV | Adopt a test-driven development's method}} +> +> One of the best development habits is to start development by writing the set of tests to which the functionality must respond. + +### Internal + +Internal tests define all the tests present in the `src/` folder of a Rust project. They have the great advantage of being able to test all the functions (even private ones) if they are placed in the same file as the project. + + +> ### Recommendation {{#check TEST-UNIT | Testing the critical path of your code}} +> It is important to test the entire critical path of your application. +> +> This way, if a future modification causes a side effect that alters its behavior, you will notice it much sooner. + +```rust +// private function +fn my_function(){ + ... // Your code here +} + +#[cfg(test)] +mod tests{ + #[test] + fn test_my_function(){ + ... // Your tests here + } +} +``` + +> ### Recommendation {{#check TEST-IGNORE | Limit the number of ignored tests}} +> +> It is recommended to limit the number of tests that will be ignored as much as possible. + +Rust has an attribute system that allows part of the code to be compiled only when necessary. +This makes it possible to define code that will only be compiled when a particular feature is requested. + +One of the basic features of any project is `test`. This allows you to describe code which will only be present when the code is compiled for testing (via the `cargo test` command). + +To do this, add the `#[cfg(test)]` attribute to the line above the function or module concerned: +```rust +#[cfg(test)] +mod test{ + + #[test] + fn test_1(){} +} +``` + +> ### Rules {{#check TEST-CFG | Wrap tests in a sub-module with the attribute `#[cfg(test)]`}} +> +> All internal tests must be wrapped in a sub-module with the `#[cfg(test)]` attribute. Similarly, any potential functions you may develop to help these tests must also have the `#[cfg(test)]` attribute. +> +> The use of a sub-module makes it possible to bring together all the tests and functions required for their proper execution. This makes it quick and easy to ensure that the code does not end up in the final binary or library and compromise the application's security. + +### Integration + +> Attention +> +> This type of test is only available for crates which are libraries. + +The integration tests are the set of tests in the `tests/` folder at the root of the crate. +In this folder, each `*.rs` file will be compiled as a different crate and the library tested will be used as if an external project were using it. + +For example, if we were developing a library called `example`, we could run the following integration test: +```rust +use example::method_name; + +#[test] +fn test_api(){ + method_name(); +} +``` + +These tests are run at the same time as all the other tests using the following command: +```bash +cargo test +``` + +> ### Rule {{#check TEST-IMPL | Check that the public behavior of the API is as expected}} +> +> Integration tests must ensure that the library's behavior is as expected. These tests must cover all the solution's public functions (including the import of types, functions, enums, etc.). +> +> This also ensures that the API is user-friendly. + +### Implementing a trait + +The example below is used to create a test to ensure that a struct or enum implements a trait. + +These tests are a little unusual. If positioned in a project, they can prevent the project from compiling if they are not valid. + +Here is an example of code used to ensure that an enum has the Send and Sync traits: + +```rust +enum Example {} + +#[cfg(test)] +mod test{ + use super::*; + + fn send_sync_trait(){} + + #[test] + fn test_traits_impl(){ + send_sync_trait::(); + } +} +``` + +> ### Recommendation {{#check TEST-TRAIT | Create tests to ensure that certain traits are implemented for structures/enums}} +> +> In certain situations, it is essential that certain struct or enum implement specific traits. This type of test is therefore highly recommended. +> +> One relevant scenario is where it is necessary to ensure that certain exposed API traits are correctly implemented. Another example, more related to the subject of this guide, concerns the validation of the implementation of the `std::hash::Hash` trait, which can be crucial in situations where data integrity is paramount. + + \ No newline at end of file diff --git a/src/en/SUMMARY.md b/src/en/SUMMARY.md index 26adcd27..7bfcf404 100644 --- a/src/en/SUMMARY.md +++ b/src/en/SUMMARY.md @@ -7,7 +7,6 @@ - [Memory management](05_memory.md) - [Type system](06_typesystem.md) - [Foreign Function Interface](07_ffi.md) +- [Test and fuzzing](08_testfuzz.md) -[Licence](LICENCE.md) - - +[Licence](LICENCE.md) \ No newline at end of file diff --git a/src/fr/01_introduction.md b/src/fr/01_introduction.md index b4fbf2d6..48a5bcd0 100644 --- a/src/fr/01_introduction.md +++ b/src/fr/01_introduction.md @@ -75,6 +75,6 @@ nous proposons des recommandations concernant l'utilisation des outils de l'écosystème Rust dans un cadre sécurisé. Ensuite, nous détaillons les précautions à prendre durant le choix et l'utilisation de bibliothèques externes. Ensuite, les recommandations à propos des constructions du langage -sont présentées. Un résumé des règles et +sont présentées. Enfin, nous discutons de la bonne utilisation des outils de +test pour un projet réalisé en Rust. Un résumé des règles et recommandations est disponible à la fin de ce guide. diff --git a/src/fr/02_devenv.md b/src/fr/02_devenv.md index 826a7b0e..fa8aeff5 100644 --- a/src/fr/02_devenv.md +++ b/src/fr/02_devenv.md @@ -69,8 +69,6 @@ $ rustup override list $ ``` - - > **Règle {{#check DENV-STABLE | Utilisation de la chaîne d'outils *stable*}}** > > Le développement d'applications sécurisées doit être mené en utilisant la @@ -284,4 +282,4 @@ la sémantique d'un programme dans certains cas. D'autres outils ou sous-commandes `cargo` utiles pour renforcer la sécurité d'un programme existent, par exemple, en recherchant des motifs de code particuliers. Nous en discutons dans les chapitres suivants en fonction de leurs -portées et de leurs objectifs. +portées et de leurs objectifs. \ No newline at end of file diff --git a/src/fr/04_language.md b/src/fr/04_language.md index d2d33e32..06a572c8 100644 --- a/src/fr/04_language.md +++ b/src/fr/04_language.md @@ -73,6 +73,19 @@ langage fournit le mot-clé `unsafe`. > afin de générer des erreurs de compilation dans le cas ou le mot-clé `unsafe` > est utilisé dans le projet. +> ### Information +> +>Il est également possible d'obtenir le même résultat en rajoutant l'un des deux blocs ci-dessous au fichier `Cargo.toml`. + +```toml +[lints.rust] +unsafe_code="forbid" +``` + +```toml +[lints.clippy] +unsafe_code = "forbid" +``` ## Dépassement d'entiers Bien que des vérifications soient effectuées par Rust en ce qui concerne les @@ -124,14 +137,12 @@ else { println!("{}", res); } ## 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é. +### Implémentation d'un type d'Erreur personnalisé + > **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 @@ -139,11 +150,16 @@ testé et jamais ignoré. > ce type doit être *exception-safe* (RFC 1236) et implémenter les traits > `Error + Send + Sync + 'static` ainsi que `Display`. +Pour s'assurer que la recommandation ci-dessus soit bien implémenter, vous pouvez vous réferez à la section +concernant [les tests de bonnes implémentations](08_testfuzz.md#Implémentation-de-trait) des traits de ce guide. + > **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. +### Utilisation de bibliothèque tierce + 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 diff --git a/src/fr/08_testfuzz.md b/src/fr/08_testfuzz.md index 1bf1ac6c..4f1c82e7 100644 --- a/src/fr/08_testfuzz.md +++ b/src/fr/08_testfuzz.md @@ -2,11 +2,124 @@ ## Test -TODO : bonnes pratiques pour écrire des tests. +Rust propose deux types de test intégrés par défaut : les tests internes ainsi que les tests d'intégration. +Dans cette section, nous discuterons de ces deux types de tests ainsi que d'un type de test un peu particulier qui est le test d'implémentation de trait. -## Fuzzing +> ### Recommandation {{#check TEST-DRIVEN-DEV | Adoptez la méthode de développement par les tests}} +> +> Une des bonnes habitudes de développement est de commencer à développer en écrivant l'ensemble des tests auquel doit répondre la fonctionnalité. +### Interne + +Les tests dits internes définissent l'ensemble des tests étant présent dans le dossier `src/` d'un projet Rust. Ceux-ci présentent le grand avantage de pouvoir tester l'ensemble des fonctions (même privées) si placé dans le même fichier que celui-ci. + +> ### Recommandation {{#check TEST-UNIT | Test du chemin critique de votre code}} +> +> Il est important de tester intégralement le chemin critique de votre code. +> +> De cette façon, si une modification future provoque un effet de bord qui altère son comportement, vous le remarquerez beaucoup plus tôt. Cela permet également de limiter le nombre de bugs au plus tôt. + +```rust +// fonction privée +fn my_function(){ + ... // Votre code ici +} + +#[cfg(test)] +mod tests{ + #[test] + fn test_my_function(){ + ... // Vos tests ici + } +} +``` + +> ### Recommandation {{#check TEST-IGNORE | Limiter au maximum le nombre de tests ignoré}} +> +> Il est recommandé de limiter au maximum le nombre de tests qui seront ignorée + +Rust possède un système d'attribue permettant de ne compiler une partie du code que si c'est nécessaire. +Cela permet notamment de définir du code qui ne sera compilé que lorsqu'une feature particulière est demandé. + +L'une des caractéristiques présente de base dans tout projet est `test`. Celle-ci permet de décrire du code qui ne sera présent que lorsque le code est compilé pour être testé (via la commande `cargo test`). + +Pour ce faire, il faut ajouter l'attribue `#[cfg(test)]` sur la ligne au dessus de la fonction ou du module concerné : +```rust +#[cfg(test)] +mod test{ + + #[test] + fn test_1(){} +} +``` + +> ### Règles {{#check TEST-CFG | Encadrer les tests dans un sous-module ayant l'attribue `#[cfg(test)]`}} +> +> L'ensemble des tests internes doivent être englobé dans un sous-module ayant l'attribue `#[cfg(test)]`. De même, les potentielles fonctions que vous pouvant être développé pour aider ces tests doivent avoir également avoir l'attribue `#[cfg(test)]`. +> +> L'utilisation d'un sous-module permet de réunir l'ensemble des tests et fonctions permettant leur bonne exécution. Cela permet de facilement et rapidement s'assurer que ce code ne se retrouvera pas dans le binaire ou la librairie finale et ne pourra compromettre la sécurité de l'application. + +### Intégration + +> ### Attention +> +> Ce type de test n'est disponible que pour les crates qui sont des librairies. + +Les tests d'intégrations sont l'ensemble de tests présents dans le dossier `tests/` à la racine de la crate. +Dans ce dossier, chaque fichier `*.rs` sera compilé comme étant une crate différentes et la bibliothèque testée sera à utiliser comme si un projet externe l'utilisait. + +Par exemple, dans si nous développions une librairie nommé `exemple`, nous pourrions faire le test d'intégration suivant : +```rust +use exemple::method_name; + +#[test] +fn test_api(){ + method_name(); +} +``` + +Ces tests sont lancés en même temps que l'ensemble des autres tests via la commande suivante : +```bash +cargo test +``` + +> ### Règle {{#check TEST-IMPL | Vérifier que le comportement publique de l'API est bien celui attendus}} +> +> Les tests d'intégration doivent permettre de s'assurer que le comportement de la librairie est celui attendu. Ces tests doivent couvrir l'ensemble des fonctionnalités publiques de la solution (y compris l'import de type, fonction, enum, etc.). +> +> Cela permets également de s'assurer de la bonne ergonomie de l'API. +### Implémentation de trait + +L'exemple ci-dessous permet de créer un test permettant de s'assurer qu'une struct ou qu'une enum implémente bien un trait. + +Ces tests sont un peu particuliers. En effet, si positionné dans un projet, alors ils peuvent empêcher le projet de se compiler s'il ne sont pas valide. + +Voici un exemple de code permettant de s'assurer qu'une enum possède bien les Traits Send et Sync : +```rust +enum Exemple {} + +#[cfg(test)] +mod test{ + use super::*; + + fn send_sync_trait(){} + + #[test] + fn test_traits_impl(){ + send_sync_trait::(); + } +} +``` + +> ### Recommandation {{#check TEST-TRAIT | Créer des tests permettant de s'assurer que certains traits sont bien implémenter pour des structures/enums}} +> +> Dans certaines situations, il est essentiel que certaines struct ou enum implémentent des traits spécifiques. Il est donc fortement recommandé de mettre en place ce type de test. +> +> Un des scénarios pertinents est celui où il est nécessaire de garantir que certains traits d'API exposés sont correctement implémentés. Un autre exemple, plus en lien avec le sujet de ce guide, concerne la validation de l'implémentation du trait `std::hash::Hash`, qui peut s'avérer crucial dans les situations où l'intégrité des données est primordiale + + \ No newline at end of file diff --git a/src/fr/SUMMARY.md b/src/fr/SUMMARY.md index 6d25a562..fbf5c7ab 100644 --- a/src/fr/SUMMARY.md +++ b/src/fr/SUMMARY.md @@ -7,7 +7,7 @@ - [Gestion de la mémoire](05_memory.md) - [Système de types](06_typesystem.md) - [FFI](07_ffi.md) +- [Test et fuzzing](08_testfuzz.md) [License](LICENSE.md) -