Rust gotchas and footguns #
This section provides a checklist that can be used during manual Rust code reviews. The list represents common issues we have encountered during our audits. It is not comprehensive, but it is a good starting point to quickly bootstrap an audit.
For safe code #
- Check string comparisons.
- Often partial-match (
starts_with,ends_with,contains) is used instead of equality. - Case (in)sensitivity of comparisons often results in issues.
- Often partial-match (
- Check string conversions to other data types (like
Vec) and vice versa. These may come with UTF-8 encoding issues. Three options to handle utf8–bytes conversions:unwrap- strict, panics on non-convertible datafrom_utf_lossy- lossy, rewrites invalid utf8 bytes to U+FFFD (replacement character)OsStr- direct, just returns the bytes
- Verify that the
with_capacitymethod of theVec,HashMap,HashSet, andIndexSetclasses (and possibly other classes) is not called with user-controlled data. Large values can lead to denial of service.- Also check that the provided capacity is smaller than the
isize::MAXbytes to prevent panics. - Note that some methods—
like Serde’s
size_hint—may indirectly expose thewith_capacitymethod.
- Also check that the provided capacity is smaller than the
- Verify that users cannot create arbitrarily deep recursive structs. A drop of such a struct can lead to a stack overflow. (See “If a Tree Falls in a Forest, Does It Overflow the Stack? for an example.)
- Verify that
std::process::exitis used sparingly. Calling this function causes a process to exit immediately, thereby sidestepping all registered drop handlers. - Verify that proper bounds checks are performed before array accesses. An out-of-bounds array access can lead to denial of service.
- Verify that proper checks are performed before type conversions to prevent loss of precision (e.g.,
u64tof64, as the mantissa of typef64is only 52 bits wide). - Check that the number of fields passed into the
serialize_structmethod matches the actual number of serialized fields. Some serialization formats, such asserde-binary, could truncate the serialized data if the number of fields is incorrect. This would mean that deserializing the data would result in a different value than the original. - Review all methods and actions that may cause panics. Other sections of the Handbook describe tools that can help with reviewing operations that could lead to panics.
unwrapandexpect(the most common panicking methods)todo!,unimplemented!,assert!andunreachable!macros- Out-of-bounds accesses
- Large allocations
- String slicing at non-character boundaries
RefCell- This struct enforces borrowing rules at runtime, and
borrow_mutcalls may panic.
- This struct enforces borrowing rules at runtime, and
HeaderMap- This struct panics after more than 32,000 elements are added.
Duration::from_secs_f{32,64}andDuration::newDuration::from_secs_f{32,64}panics with negative inputs;Duration::newpanics when the nanoseconds value overflows into the seconds counter.
- Verify that it is not possible to modify keys while they’re in a collection type like a
HashMaporBinaryHeap, as this leads to undefined behavior. - Verify that
debug_assert!and other debug macros are not used for actual data validation. Such macros are removed from production builds. - Verify that raw file descriptors are explicitly closed in all execution flow paths. Raw descriptors are not closed on
Drop.- Verify that file descriptors are not closed two times: automatically on
Dropand explicitly via theclosemethod.
- Verify that file descriptors are not closed two times: automatically on
- Explicitly flush
BufWriters to get flush errors; errors are ignored on automatic flushing when values are dropped. - Ensure that absolute paths are not used with
PathBuf::join, as this may lead to path traversal issues. - Verify that functions used only in tests are guarded by
#[cfg(test)]. - Verify that each use of
#![allow(...)]is justified and that#[allow(...)]is not used excessively. - Operator precedence of bitwise operators (
&,^,|) compared to comparison operators (==,!=) differs between Rust and C. This is something to be aware of when rewriting C code. - Verify that test-only Cargo features (like mocks) are not included in
[dependencies]and are not part of thedefaultfeature set. Usecargo tree -e featuresto validate your project. - Review code against possible issues resulting from operating system interactions (see the C/C++ chapter for ideas). Any syscall, libc function call, and other interaction with the operating system should be checked against known gotchas.
- Review the “Secure Rust Guidelines checklist”.
For unsafe code #
Common issues to check for in unsafe code are given below. For more information, read the Rustonomicon.
- Any union access is unsafe in Rust. Verify that the union field that matches the underlying data is used.
- Look for uses of libc APIs like
memsetormemcpy. Most of them can be replaced with safe Rust counterparts. - If
#repr(packed)is used on a struct, then check thatwrite_unalignedis used for unaligned fields. - Check for uses of
std::mem::uninitialized. - Check for uses of
std::mem::forget. - Check for uses of
transmuteorcastfrom a non-mutable reference&to a mutable&mut(likely an undefined behavior). - Review uses of
static mutand recommend using synchronization instead.