|
6 | 6 | Expressions |
7 | 7 | =========== |
8 | 8 |
|
| 9 | + |
| 10 | +.. guideline:: Ensure that integer operations do not result in arithmetic overflow |
| 11 | + :id: gui_dCquvqE1csI3 |
| 12 | + :category: required |
| 13 | + :status: draft |
| 14 | + :release: 1.0 - latest |
| 15 | + :fls: fls_oFIRXBPXu6Zv |
| 16 | + :decidability: decidable |
| 17 | + :scope: system |
| 18 | + :tags: security, performance, numerics |
| 19 | + |
| 20 | + Eliminate `arithmetic overflow <https://rust-lang.github.io/fls/expressions.html#arithmetic-overflow>`_ of both signed and unsigned integer types. |
| 21 | + Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes. |
| 22 | + |
| 23 | + This rule applies to the following primitive types: |
| 24 | + |
| 25 | + * ``i8`` |
| 26 | + * ``i16`` |
| 27 | + * ``i32`` |
| 28 | + * ``i64`` |
| 29 | + * ``i128`` |
| 30 | + * ``u8`` |
| 31 | + * ``u16`` |
| 32 | + * ``u32`` |
| 33 | + * ``u64`` |
| 34 | + * ``u128`` |
| 35 | + * ``usize`` |
| 36 | + * ``isize`` |
| 37 | + |
| 38 | + .. rationale:: |
| 39 | + :id: rat_LvrS1jTCXEOk |
| 40 | + :status: draft |
| 41 | + |
| 42 | + Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior. |
| 43 | + Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior. |
| 44 | + Use explicit `wrapping <https://doc.rust-lang.org/std/num/struct.Wrapping.html>`_ or |
| 45 | + `saturating <https://doc.rust-lang.org/std/num/struct.Saturating.html>`_ semantics where these behaviors are intentional. |
| 46 | + Range checking can be used to eliminate the possibility of arithmetic overflow. |
| 47 | + |
| 48 | + .. non_compliant_example:: |
| 49 | + :id: non_compl_ex_cCh2RQUXeH0N |
| 50 | + :status: draft |
| 51 | + |
| 52 | + This noncompliant code example can result in arithmetic overflow during the addition of the signed operands ``si_a`` and ``si_b``: |
| 53 | + |
| 54 | + .. code-block:: rust |
| 55 | +
|
| 56 | + fn add(si_a: i32, si_b: i32) { |
| 57 | + let sum: i32 = si_a + si_b; |
| 58 | + // ... |
| 59 | + } |
| 60 | +
|
| 61 | + .. compliant_example:: |
| 62 | + :id: compl_ex_BgUHiRB4kc4b_1 |
| 63 | + :status: draft |
| 64 | + |
| 65 | + This compliant solution ensures that the addition operation cannot result in arithmetic overflow, |
| 66 | + based on the maximum range of a signed 32-bit integer. |
| 67 | + Functions such as |
| 68 | + `overflowing_add <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_add>`_, |
| 69 | + `overflowing_sub <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_sub>`_, and |
| 70 | + `overflowing_mul <https://doc.rust-lang.org/stable/core/primitive.u32.html#method.overflowing_mul>`_ |
| 71 | + can also be used to detect overflow. |
| 72 | + Code that invoked these functions would typically further restrict the range of possible values, |
| 73 | + based on the anticipated range of the inputs. |
| 74 | + |
| 75 | + .. code-block:: rust |
| 76 | +
|
| 77 | + enum ArithmeticError { |
| 78 | + Overflow, |
| 79 | + DivisionByZero, |
| 80 | + } |
| 81 | +
|
| 82 | + use std::i32::{MAX as INT_MAX, MIN as INT_MIN}; |
| 83 | +
|
| 84 | + fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> { |
| 85 | + if (si_b > 0 && si_a > INT_MAX - si_b) |
| 86 | + || (si_b < 0 && si_a < INT_MIN - si_b) |
| 87 | + { |
| 88 | + Err(ArithmeticError::Overflow) |
| 89 | + } else { |
| 90 | + Ok(si_a + si_b) |
| 91 | + } |
| 92 | + } |
| 93 | +
|
| 94 | + fn sub(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> { |
| 95 | + if (si_b < 0 && si_a > INT_MAX + si_b) |
| 96 | + || (si_b > 0 && si_a < INT_MIN + si_b) |
| 97 | + { |
| 98 | + Err(ArithmeticError::Overflow) |
| 99 | + } else { |
| 100 | + Ok(si_a - si_b) |
| 101 | + } |
| 102 | + } |
| 103 | +
|
| 104 | + fn mul(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> { |
| 105 | + if si_a == 0 || si_b == 0 { |
| 106 | + return Ok(0); |
| 107 | + } |
| 108 | +
|
| 109 | + // Detect overflow before performing multiplication |
| 110 | + if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) { |
| 111 | + Err(ArithmeticError::Overflow) |
| 112 | + } else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a)) |
| 113 | + || (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a)) |
| 114 | + { |
| 115 | + Err(ArithmeticError::Overflow) |
| 116 | + } else { |
| 117 | + Ok(si_a * si_b) |
| 118 | + } |
| 119 | + } |
| 120 | +
|
| 121 | + .. compliant_example:: |
| 122 | + :id: compl_ex_BgUHiRB4kc4c |
| 123 | + :status: draft |
| 124 | + |
| 125 | + This compliant example uses safe checked addition instead of manual bounds checks. |
| 126 | + Checked functions can reduce readability when complex arithmetic expressions are needed. |
| 127 | + |
| 128 | + .. code-block:: rust |
| 129 | +
|
| 130 | + fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> { |
| 131 | + si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow) |
| 132 | + } |
| 133 | +
|
| 134 | + fn sub(a: i32, b: i32) -> Result<i32, ArithmeticError> { |
| 135 | + a.checked_sub(b).ok_or(ArithmeticError::Overflow) |
| 136 | + } |
| 137 | +
|
| 138 | + fn mul(a: i32, b: i32) -> Result<i32, ArithmeticError> { |
| 139 | + a.checked_mul(b).ok_or(ArithmeticError::Overflow) |
| 140 | + } |
| 141 | +
|
| 142 | + .. compliant_example:: |
| 143 | + :id: compl_ex_BgUHiRB4kc4b |
| 144 | + :status: draft |
| 145 | + |
| 146 | + Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions. |
| 147 | + |
| 148 | + .. code-block:: rust |
| 149 | +
|
| 150 | + fn add(a: i32, b: i32) -> i32 { |
| 151 | + a.wrapping_add(b) |
| 152 | + } |
| 153 | +
|
| 154 | + fn sub(a: i32, b: i32) -> i32 { |
| 155 | + a.wrapping_sub(b) |
| 156 | + } |
| 157 | +
|
| 158 | + fn mul(a: i32, b: i32) -> i32 { |
| 159 | + a.wrapping_mul(b) |
| 160 | + } |
| 161 | +
|
| 162 | + .. compliant_example:: |
| 163 | + :id: compl_ex_BhUHiRB4kc4b |
| 164 | + :status: draft |
| 165 | + |
| 166 | + Wrapping behavior call also be achieved using the ``Wrapping<T>`` type as in this compliant solution. |
| 167 | + The ``Wrapping<T>`` type is a ``struct`` found in the ``std::num`` module that explicitly enables two's complement |
| 168 | + wrapping arithmetic for the inner type ``T`` (which must be an integer or ``usize/isize``). |
| 169 | + The ``Wrapping<T>`` type provides a consistent way to force wrapping behavior in all build modes, |
| 170 | + which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior. |
| 171 | + |
| 172 | + .. code-block:: rust |
| 173 | +
|
| 174 | + use std::num::Wrapping; |
| 175 | +
|
| 176 | + fn add(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> { |
| 177 | + si_a + si_b |
| 178 | + } |
| 179 | +
|
| 180 | + fn sub(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> { |
| 181 | + si_a - si_b |
| 182 | + } |
| 183 | +
|
| 184 | + fn mul(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> { |
| 185 | + si_a * si_b |
| 186 | + } |
| 187 | +
|
| 188 | + fn main() { |
| 189 | + let si_a = Wrapping(i32::MAX); |
| 190 | + let si_b = Wrapping(i32::MAX); |
| 191 | + println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) |
| 192 | + } |
| 193 | +
|
| 194 | + .. compliant_example:: |
| 195 | + :id: compl_ex_BgUHiSB4kc4b |
| 196 | + :status: draft |
| 197 | + |
| 198 | + Saturation semantics means that instead of wrapping around or resulting in an error, |
| 199 | + any result that falls outside the valid range of the integer type is clamped: |
| 200 | + |
| 201 | + - To the maximum value, if the result were to be greater than the maximum value, or |
| 202 | + - To the minimum value, if the result were to be smaller than the minimum, |
| 203 | + |
| 204 | + Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow. |
| 205 | + This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations. |
| 206 | + |
| 207 | + .. code-block:: rust |
| 208 | +
|
| 209 | + fn add(a: i32, b: i32) -> i32 { |
| 210 | + a.saturating_add(b) |
| 211 | + } |
| 212 | +
|
| 213 | + fn sub(a: i32, b: i32) -> i32 { |
| 214 | + a.saturating_sub(b) |
| 215 | + } |
| 216 | +
|
| 217 | + fn mul(a: i32, b: i32) -> i32 { |
| 218 | + a.saturating_mul(b) |
| 219 | + } |
| 220 | +
|
| 221 | + .. compliant_example:: |
| 222 | + :id: compl_ex_BgUHiSB4kd4b |
| 223 | + :status: draft |
| 224 | + |
| 225 | + ``Saturating<T>`` is a wrapper type in Rust’s ``core`` library (``core::num::Saturating<T>``) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing. |
| 226 | + ``Saturating<T>`` is useful when you have a section of code or a data type where all arithmetic must be saturating. |
| 227 | + This compliant solution uses the ``Saturating<T>`` type to define several functions that perform basic integer operations using saturation semantics. |
| 228 | + |
| 229 | + .. code-block:: rust |
| 230 | +
|
| 231 | + use std::num::Saturating; |
| 232 | +
|
| 233 | + fn add(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> { |
| 234 | + si_a + si_b |
| 235 | + } |
| 236 | +
|
| 237 | + fn sub(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> { |
| 238 | + si_a - si_b |
| 239 | + } |
| 240 | +
|
| 241 | + fn mul(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> { |
| 242 | + si_a * si_b |
| 243 | + } |
| 244 | +
|
| 245 | + fn main() { |
| 246 | + let si_a = Saturating(i32::MAX); |
| 247 | + let si_b = Saturating(i32::MAX); |
| 248 | + println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) |
| 249 | + } |
| 250 | +
|
| 251 | + .. non_compliant_example:: |
| 252 | + :id: non_compl_ex_cCh2RQUXeH0O |
| 253 | + :status: draft |
| 254 | + |
| 255 | + This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow. |
| 256 | + |
| 257 | + .. code-block:: rust |
| 258 | +
|
| 259 | + fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> { |
| 260 | + if s_b == 0 { |
| 261 | + Err(DivError::DivisionByZero) |
| 262 | + } else { |
| 263 | + Ok(s_a / s_b) |
| 264 | + } |
| 265 | + } |
| 266 | +
|
| 267 | + .. compliant_example:: |
| 268 | + :id: compl_ex_BgUHiRB4kc4d |
| 269 | + :status: draft |
| 270 | + |
| 271 | + This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow: |
| 272 | + |
| 273 | + .. code-block:: rust |
| 274 | +
|
| 275 | +
|
| 276 | + fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> { |
| 277 | + if s_b == 0 { |
| 278 | + Err("division by zero") |
| 279 | + } else if s_a == i64::MIN && s_b == -1 { |
| 280 | + Err("arithmetic overflow") |
| 281 | + } else { |
| 282 | + Ok(s_a / s_b) |
| 283 | + } |
| 284 | + } |
| 285 | +
|
9 | 286 | .. guideline:: Avoid as underscore pointer casts |
10 | 287 | :id: gui_HDnAZ7EZ4z6G |
11 | 288 | :category: required |
@@ -504,7 +781,6 @@ Expressions |
504 | 781 | /* ... */ |
505 | 782 | } |
506 | 783 |
|
507 | | -
|
508 | 784 | .. guideline:: Integer shift shall only be performed through `checked_` APIs |
509 | 785 | :id: gui_RHvQj8BHlz9b |
510 | 786 | :category: required |
@@ -790,3 +1066,4 @@ Expressions |
790 | 1066 | println!("Performing {bits} << {sh} would be meaningless and crash-prone; we avoided it!"); |
791 | 1067 | } |
792 | 1068 | } |
| 1069 | +
|
0 commit comments