Rust Cross Reference with CERT-C 2016
The following tables provide a cross reference between the CERT-C 2025 guidelines and their applicability to Rust. The first table covers guidelines that are applicable to Rust in general, while the second table covers additional guidelines that are applicable in the presence of unsafe code. The third table lists guidelines that are not applicable to Rust.
Table 1 – Guidelines applicable to Rust in general (safe Rust, no unsafe code present)
Guideline |
Title |
Category |
Safety Critical rust Rule |
Comment |
|---|---|---|---|---|
Do not use object representations to compare floating-point values |
It might not be immediately obvious where this might be done on
purpose. It is certainly not something one would imagine
happening in a self-contained system that’s using floating point
numbers. However, when receiving raw floating point data, it
can be extremely tempting to “just compare the bits”. What’s
worse is that this approach would work for some values, which
might mask its lack of correctness. For completeness, it would be
good to mention examples of FP values with different encodings
that however compare equal under the standard. The simplest one
here is, I think, |
|||
Avoid information leakage when passing a structure across a trust boundary |
It seems to be technically possible to achieve, using Unsafe. Zulip thread of this discussion: How should padding bits be erased for sending?The important issue seems to be that of copying into the untrusted buffer without copying sensitive data that may lie in the uninitialized bits of the copied struct.For structs and enums, this seems to be solvable by making a type which copies everything except for the padding bits inside of it.For unions, one might want to be REALLY SURE, and hopefully prove, that you’re not copying your padding. Might be impossible to safely send unions.One might want also to zero-out sensitive memory in their own processes, such that nothing sensitive is left behind after they end. That’s outside the scope of our work. Zulip thread on this: Are bits erased on drop? If not, how can I ensure they are? |
|||
Converting a pointer to integer or integer to pointer |
With modifications:- ptr2int: considered to be relatively unimportant- int2ptr: cautioned againstPointer Provenance plays a role in this.Also, see: transmute: caution against int2ptr transmutationMay deserve scrutiny for possible unsafe-heavy treatment |
|||
Do not refer to an atomic variable twice in an expression |
||||
Wrap functions that can spuriously wake up in a loop |
This directly maps to the usage of
|
|||
Do not modify constant objects |
|
|||
Do not attempt to modify string literals |
Treat as merged with EXP40 for Rust mapping purposes |
|||
Ensure that integer operations do not wrap |
Already implemented; still review mapping/category |
|||
Ensure that division and remainder operations do not result in divide-by-zero errors |
Already implemented; still review mapping/category |
|||
Do not form or use out-of-bounds pointers or array subscripts |
In Safe Rust:- Direct addressing will panic if OOB- arr.get(index) will return None if OOBIn Unsafe Rust:- arr.get_unchecked(index) is UB if OOB.So, I’d say out-of-bound indexing should be either prevented or handled properly.If using Unsafe indexing, it should be formally validated. |
|||
Avoid TOCTOU race conditions while accessing files |
Rationale:
|
|||
Do not use floating-point variables as loop counters |
||||
Ensure that integer conversions do not result in lost or misinterpreted data |
Already being worked in #185; still review mapping/category |
|||
Prevent or detect domain and range errors in math functions |
||||
Do not call system() |
The mapping is not exact (it doesn’t involve system()):If running commands, their input must always be sanitized, and their behavior should be made portable.There is a Clippy lint that touches on this topic. We probably want a set of rules to address how system commands may be called. |
|||
Never hard code sensitive information |
||||
Free dynamically allocated memory when no longer needed |
The Rust compiler mostly enforces this automatically.Important
exceptions in Safe Rust are
|
|||
Ensure that operations on signed integers do not result in overflow |
Already implemented; still review mapping/category |
|||
Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand |
Already implemented; still review mapping/category |
|||
Avoid race conditions when using library functions |
||||
Wrap functions that can spuriously fail in a loop |
||||
Do not add or subtract an integer to a pointer to a non-array object |
||||
Ensure that floating-point conversions are within range of the new type |
||||
Preserve precision when converting integral values to floating-point type |
||||
Close files when they are no longer needed |
This is a good precept for life, I guess.In Rust, handling of
such resources is a lot easier since their Drop implementation
should close them automatically.It’s still good to keep in mind,
and not make a file e.g. have the |
|||
Preserve thread safety and liveness when using condition variables |
So far, the consensus is to make the following recommendations:-
Consider not using ``Condvar`` if at all possible.- If using
``Condvar`` and liveness is critical, consider using
``notify_all`` instead of ``notify_one``.Zulip discussion:
Recommending ``Condvar::notify_all` instead of
|
|||
Properly seed pseudorandom number generators |
||||
Only free memory allocated dynamically |
||||
Avoid deadlock by locking in a predefined order |
Table 2 – Guidelines applicable to Rust in the presence of unsafe code
Guideline |
Title |
Category |
Safety Critical rust Rule |
Comment |
|---|---|---|---|---|
Do not access a variable through a pointer of an incompatible type |
||||
Prevent data races when accessing bit-fields from multiple threads |
||||
Do not allow data races in multithreaded code |
Treat as paired with CON32 in Rust, as their requirements are the same |
|||
Do not subtract or compare two pointers that do not refer to the same array |
||||
Do not access freed memory |
||||
Do not dereference null pointers |
||||
Declare objects with appropriate storage durations |
||||
Do not compare padding data |
||||
Do not rely on an environment pointer following an operation that may invalidate it |
Recently moved into the unsafe bucket This interaction maps to
Rust through the
|
|||
Do not read uninitialized memory |
Rationale: it’s UB in Rust |
|||
Do not modify the alignment of objects by calling realloc() |
||||
Declare objects shared between threads with appropriate storage durations |
||||
Do not cast pointers into more strictly aligned pointer types |
||||
Allocate sufficient memory for an object |
Table 3 – Guideline rules that are not applicable to Rust
Guideline |
Title |
Category |
Safety Critical rust Rule |
Comment |
|---|---|---|---|---|
Avoid side effects in arguments to unsafe macros |
Context In C, “unsafe macro” means a macro that evaluates
one of its parameters more than once or not at all. Doing so with
side effects then means that those side effects are triggered
either more than once, or not at all.C macros make using this
kind of pattern safely very hard.In Rust, this is not the case,
and there are multiple examples of macros that use this pattern
on purpose and successfully.There are multiple different use
cases for this, in fact, as explained in this comment on
Zulip.
Furthermore, if no side effecting is required, then the writer of
the macro can enforce this using |
|||
Do not depend on the order of evaluation for side effects |
Unlike for C, the evaluation order is fully defined in Rust:
expressions -> expression
precedenceThus,
this rule isn’t really needed. At least, not with the same
UB-stopping urgency that it is needed for in C.Extra notes:
there is a restriction lint that touches on this topic, as a
precedent:
|
|||
Do not access a volatile object through a nonvolatile reference |
Volatile is specific to C in this case.Furthermore, a mismatch like this would be bound by the rules of the type system, and thus be caught unless the user were to be using transmutation. Rules about transmutation, however, are not a map of this rule but they are rather their own thing. |
|||
Do not add or subtract a scaled integer to a pointer |
Pointer arithmetic is done through APIs in Rust, all of
which
mention up-front that their |
|||
Do not perform operations on devices that are only appropriate for files |
Context In C, those APIs might block (the current thread)
if the files being open correspond to devices (files under
``/dev/``). This in turn can lead to a Denial of Service, by
effectively blocking the system. The APIs present in
|
|||
Do not access a closed file |
This is guaranteed in Rust already by the fact that closing a file consumes the variable, invalidating any future accesses to it.Unsafe Rust, as always, could get away with doing something like this regardless, but that would be UB and should be covered by a different guideline (something along the lines of “don’t extend lifetimes beyond their valid range” |
|||
Do not store pointers returned by certain functions |
Live References cannot be invalidated in Rust.Implementors of such libraries in Unsafe Rust must take care to not invalidate the references they create. This would be UB. It would always violate the memory model. But the rule to enforce this lies outside of the context of ENV34. It’s a more general rule that must be followed. |
|||
Do not call signal() in a multithreaded program |
Rust doesn’t provide support for signal handling in |
|||
Do not use the rand() function for generating pseudorandom numbers |
The APIs for generating pseudorandom numbers in core and std are currently nightly-only.I don’t know if it was RNG, but IIRC something like it from the stdlib was discouraged in Rust as well, due to it not being collision resistant. I’m pretty sure it was a hash function or something. |
|||
Guarantee that storage for strings has sufficient space for character data and the null terminator |
Strings are not null-terminated in Rust and their storage space is guaranteed |
|||
Do not create incompatible declarations of the same function or object |
Rationale: doing this is impossible in Rust. |
|||
Do not create a universal character name through concatenation |
Rationale: it’s just very specific to C |
|||
Use the correct syntax when declaring a flexible array member |
The rule is about “using the standardized syntax for this feature, instead of relying on Undefined Behavior and a compiler-specific, non-standardized syntax”.Such a suggestion makes no sense in Rust, since there is only one compiler and its syntax is the only one available. Correct syntax can compile; incorrect syntax cannot. |
|||
Do not modify objects with temporary lifetime |
Rationale: the borrow checker eliminates this problem |
|||
Avoid Undefined Behavior when using restrict-qualified pointers |
The borrow checker gives us correct-by-construction restrict qualifications in codegen. Basically, “restrict” is part of the type system, and type checking (in particular borrow checking) enforces that its rules are always abided by. |
|||
Ensure size arguments for variable length arrays are in a valid range |
Rationale: VLAs are not a thing in Rust |
|||
Guarantee that library functions do not form invalid pointers |
The rule basically asks the user that, whenever they call a function that receives a pointer to an array and a length, that said length doesn’t go outside the bounds of the array (which would form an invalid pointer)In Safe Rust, this is handled by bounds checks, and in the mapping of ARR30 we’re already handling that.In Unsafe Rust, doing this is to break the Safety Contract of the API we’re calling, which is getting its own rule. and is outside the mapping of this ARR38 (this rule) Recently moved out of the applicable bucket |
|||
Do not pass a non-null-terminated character sequence to a library function that expects a string |
Rationale: strings are not null-terminated in Rust |
|||
Distinguish between characters read from a file and EOF or WEOF |
See the Read trait. EOF / WEOF cannot be acquired from them.As long as file reading operations are done through the Read trait, this should not be a concern. |
|||
Do not assume that fgets() or fgetws() returns a nonempty string when successful |
This rule comes from the need to properly handle errors found
during the execution of |
|||
Do not alternately input and output from a stream without an intervening flush or positioning call |
This is UB in C, due to implementation details of the stream handling APIs. The implementation of those APIs in Rust is different, so the guideline doesn’t really map to it. For more information, see this comment and this issue. |
|||
Only use values for fsetpos() that are returned from fgetpos() |
The closest APIs in Rust’s |
|||
Detect and handle standard library errors |
Functions in std that may fail, wrap their result in the
|
|||
Ensure that control never reaches the end of a non-void function |
Rationale: this is already enforced by the type system |
|||
Do not use preprocessor directives in invocations of function-like macros |
This rule is needed in C due to how its preprocessor works. It’s very C-preprocessor-specific, and doesn’t map to Rust |
|||
Declare identifiers before using them |
Inferred types in Rust must always type-check. Not doing so results in a compile-time error. Therefore, the use case for this rule doesn’t exist in Rust. |
|||
Do not declare or define a reserved identifier |
Context In C, reserved identifiers include more than just
keywords. They include library functions and variables (eg
``memcpy``, ``errno``)), including some well-known ones (eg
``abs``). C also lists many identifiers as potentially reserved
(eg anything beginning with ``str``). It is possible to redefine
(and thus override) such global identifiers in C, such as by
adding |
|||
Call functions with the correct number and type of arguments |
This is enforced by the type system |
|||
Do not rely on side effects in operands to sizeof, _Alignof or _Generic |
This is C-specific |
|||
Do not call va_arg with an argument of the incorrect type |
Variadic Functions don’t exist in Safe Rust. They do exist in Unsafe Rust, in the form of C’s Variadic Functions. These exist as a way to declare and implement variadic functions for FFI contexts.That being said, this rule doesn’t map to Rust, because while it is possible to implement C variadic functions incorrectly, that issue is covered by the more general rule of Callers of any unsafe fn shall abide by the function’s Safety contract.Addendum: it is very reasonable to argue for a rule along the lines of “C-variadic functions should not be used outside of FFI contexts”, but such a rule would not be a mapping of CERT-C’s EXP47 rule. That is left for future work. |
|||
Cast characters to unsigned char before converting to larger integer sizes |
Characters in Rust are not integers.They are unicode scalar values. |
|||
Exclude user input from format strings |
Format strings are evaluated at compile time in Rust |
|||
Do not call getc(), putc(), getwc() or putwc() with a stream argument that has side effects |
This rule arises from how those APIs are implemented (as C macros), and not from a more general aspect of the situation. |
|||
Do not modify the object referenced by the return value of certain functions |
Immutability, when required, is always enforced in Rust.I believe that Unsafe Rust could get away with doing this, jumping through some hoops. But that would lie in the vecinity of “Do not modify values whose validity invariant requires them to be immutable”. Breaking validity invariants is, of course, UB. |
|||
All exit handlers must return normally |
Panicking doesn’t have UB. Panics inside of panics will eventually abort the program. |
|||
Detect errors when converting a string to a number |
Functions in std that may fail, wrap their result in the
|
|||
Do not declare an identifier with conflicting linkage classifications |
This is a very specific issue to C |
|||
Do not declare variables inside a switch statement before the first case label |
The semantics of C’s |
|||
Do not perform assignments in selection statements |
Rust is expression-oriented. Assignments return |
|||
Do not use a bitwise operator with a Boolean-like operand |
The type system makes usage of these operators a reasonable thing in Rust. They are well-defined and well-behaved. |
|||
Do not copy a FILE object |
This is related to how the |
|||
Reset strings on fgets() or fgetws() failure |
Unlike in the original |
|||
Call only asynchronous-safe functions within signal handlers |
Rust doesn’t provide support for signal handling in |
|||
Do not access shared objects in signal handlers |
Same as SIG30 |
|||
Take care when reading errno |
Errno doesn’t exist in Rust.But furthermore, functions that may
fail wrap their result in the |
|||
Do not rely on indeterminate values of errno |
Rationale: see ERR30-C |
|||
Clean up thread-specific storage |
Thread-specific storage in Rust refers to values behind a
|
|||
Do not join or detach a thread that was previously joined or detached |
This does not apply to Rust because:-
|
|||
Use correct integer precisions |
All primitive integer types guarantee their precision in Rust |
|||
Arguments to character-handling functions must be representable as an unsigned char |
Characters in Rust are not integers |
|||
Do not confuse narrow and wide character strings and functions |
There’s only one character type in Rust |
|||
Allocate and copy structures containing a flexible array member dynamically |
Flexible array members are not a thing in Rust |
|||
Use valid format strings |
Format strings are evaluated, and therefore, validated, at compile time, in Rust |
|||
Do not call signal() from within interruptible signal handlers |
Rust doesn’t provide support for signal handling in |
|||
Do not return from a computational exception signal handler |
Rationale: see SIG34 |
|||
Do not destroy a mutex while it is locked |
This is impossible to do in Rust due to ownership semantics: it’s
only possible to destroy things one has |
|||
Do not pass invalid data to the asctime() function |
There is no equivalent time-displaying function in either
|
|||
Do not treat a predefined identifier as an object if it might only be implemented as a macro |
There is no such thing as a macro pointer in Rust |
|||
Do not call va_arg() on a va_list that has an indeterminate value |
Variadic functions do not exist in Rust |
|||
Do not violate constraints |
Context of MSC40: this rule is about not writing code that doesn’t comply with the C standard.The Rust compiler rejects all such programs. If a program is not valid Rust code, then it does not compile. |