diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3b2f5331..02ccbdb2 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Hello World!](hello-world.md) - [Small Example](hello-world/small-example.md) - [Why Rust?](why-rust.md) + - [An Example in C](why-rust/an-example-in-c.md) - [Compile Time Guarantees](why-rust/compile-time.md) - [Runtime Guarantees](why-rust/runtime.md) - [Modern Features](why-rust/modern.md) diff --git a/src/credits.md b/src/credits.md index 99475f0b..28703b36 100644 --- a/src/credits.md +++ b/src/credits.md @@ -28,3 +28,11 @@ terms. The [Interoperability with C++](android/interoperability/cpp.md) section uses an image from [CXX](https://cxx.rs/). Please see the `third_party/cxx/` directory for details, including the license terms. + +## An Example in C + +The [Why Rust? - An Example in C](why-rust/an-example-in-c.md) section has been +taken from the presentation slides of [Colin Finck's Master +Thesis](https://colinfinck.de/Master_Thesis_Colin_Finck.pdf). It has been +relicensed under the terms of the Apache 2.0 license for this course by the +author. diff --git a/src/why-rust/an-example-in-c.md b/src/why-rust/an-example-in-c.md new file mode 100644 index 00000000..d0b7d248 --- /dev/null +++ b/src/why-rust/an-example-in-c.md @@ -0,0 +1,90 @@ +# An Example in C + + +Let's consider the following "minimum wrong example" program in C: + +```c,editable +#include +#include +#include + +int main(int argc, char* argv[]) { + char *buf, *filename; + FILE *fp; + size_t bytes, len; + struct stat st; + + switch (argc) { + case 1: + printf("Too few arguments!\n"); + return 1; + + case 2: + filename = argv[argc]; + stat(filename, &st); + len = st.st_size; + + buf = (char*)malloc(len); + if (!buf) + printf("malloc failed!\n", len); + return 1; + + fp = fopen(filename, "rb"); + bytes = fread(buf, 1, len, fp); + if (bytes = st.st_size) + printf("%s", buf); + else + printf("fread failed!\n"); + + case 3: + printf("Too many arguments!\n"); + return 1; + } + + return 0; +} +``` + +How many bugs do you spot? + +
+ +Despite just 29 lines of code, this C example contains serious bugs in at least 11: + +1. Assignment `=` instead of equality comparison `==` (line 28) +2. Excess argument to `printf` (line 23) +3. File descriptor leak (after line 26) +4. Forgotten braces in multi-line `if` (line 22) +5. Forgotten `break` in a `switch` statement (line 32) +6. Forgotten NUL-termination of the `buf` string, leading to a buffer overflow (line 29) +7. Memory leak by not freeing the `malloc`-allocated buffer (line 21) +8. Out-of-bounds access (line 17) +9. Unchecked cases in the `switch` statement (line 11) +10. Unchecked return values of `stat` and `fopen` (lines 18 and 26) + +_Shouldn't these bugs be obvious even for a C compiler?_ +No, surprisingly this code compiles warning-free at the default warning level, even in the latest GCC version (13.2 as of writing). + +_Isn't this a highly unrealistic example?_ +Absolutely not, these kind of bugs have lead to serious security vulnerabilities in the past. Some examples: + +* Assignment `=` instead of equality comparison `==`: [The Linux Backdoor Attempt of 2003](https://freedom-to-tinker.com/2013/10/09/the-linux-backdoor-attempt-of-2003) +* Forgotten braces in multi-line `if`: [The Apple goto fail vulnerability](https://dwheeler.com/essays/apple-goto-fail.html) +* Forgotten `break` in a `switch` statement: [The break that broke sudo](https://nakedsecurity.sophos.com/2012/05/21/anatomy-of-a-security-hole-the-break-that-broke-sudo) + +_How is Rust any better here?_ +Safe Rust makes all of these bugs impossible: + +1. Assignments inside an `if` clause are not supported. +2. Format strings are checked at compile-time. +3. Resources are freed at the end of scope via the `Drop` trait. +4. All `if` clauses require braces. +5. `match` (as the Rust equivalent to `switch`) does not fall-through, hence you can't accidentally forget a `break`. +6. Buffer slices carry their size and don't rely on a NUL terminator. +7. Heap-allocated memory is freed via the `Drop` trait when the corresponding `Box` leaves the scope. +8. Out-of-bounds accesses cause a panic or can be checked via the `get` method of a slice. +9. `match` mandates that all cases are handled. +10. Fallible Rust functions return `Result` values that need to be unwrapped and thereby checked for success. + Additionally, the compiler emits a warning if you miss to check the return value of a function marked with `#[must_use]`. + +