|
6 | 6 | Types and Traits |
7 | 7 | ================ |
8 | 8 |
|
| 9 | +.. SPDX-License-Identifier: MIT OR Apache-2.0 |
| 10 | + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors |
| 11 | +
|
| 12 | +.. default-domain:: coding-guidelines |
| 13 | + |
| 14 | +.. guideline:: Ensure reads of union fields produce valid values for the field's type |
| 15 | + :id: gui_UnionFieldValidity |
| 16 | + :category: required |
| 17 | + :status: draft |
| 18 | + :release: 1.85.0 |
| 19 | + :decidability: undecidable |
| 20 | + :scope: expression |
| 21 | + :tags: unions, safety, undefined-behavior |
| 22 | + |
| 23 | + When reading from a union field, ensure that the underlying bytes constitute a valid value |
| 24 | + for that field's type. |
| 25 | + Reading a union field whose bytes do not represent a valid value |
| 26 | + for the field's type is undefined behavior. |
| 27 | + |
| 28 | + Before accessing a union field: |
| 29 | + |
| 30 | + * Verify that the union was last written through that field, or |
| 31 | + * Verify that the union was written through a field whose bytes are valid when reinterpreted as the target field's type, or |
| 32 | + * Use explicit validity checks when the active field is uncertain |
| 33 | + |
| 34 | + .. rationale:: |
| 35 | + :id: rat_UnionFieldValidityReason |
| 36 | + :status: draft |
| 37 | + |
| 38 | + Unions allow multiple fields to occupy the same memory, similar to C unions. |
| 39 | + Unlike enums, unions do not track which field is currently active. It is the programmer's |
| 40 | + responsibility to ensure that when a field is read, the underlying bytes are valid for |
| 41 | + that field's type [RUST-REF-UNION]_. |
| 42 | + |
| 43 | + Every type has a *validity invariant* — a set of constraints that all values of |
| 44 | + that type must satisfy [UCG-VALIDITY]_. |
| 45 | + Reading a union field performs a *typed read*, |
| 46 | + which asserts that the bytes are valid for the target type. |
| 47 | + Violating this invariant is undefined behavior. |
| 48 | + |
| 49 | + Examples of validity requirements for common types: |
| 50 | + |
| 51 | + * **bool**: Must be ``0`` (false) or ``1`` (true). Any other value (e.g., ``3``) is invalid. |
| 52 | + * **char**: Must be a valid Unicode scalar value (0x0 to 0xD7FF or 0xE000 to 0x10FFFF). |
| 53 | + * **References**: Must be non-null and properly aligned. |
| 54 | + * **Enums**: Must hold a valid discriminant value. |
| 55 | + * **Floating point**: All bit patterns are valid for the ``f32`` or ``f64``.type |
| 56 | + * **Integers**: All bit patterns are valid for integer types. |
| 57 | + |
| 58 | + Consequences of reading invalid values include: |
| 59 | + |
| 60 | + * Immediate undefined behavior, even if the value is not used |
| 61 | + * Miscompilation due to compiler assumptions about valid values |
| 62 | + * Security vulnerabilities from unexpected program behavior |
| 63 | + * Non-deterministic behavior that varies across optimization levels or platforms |
| 64 | + |
| 65 | + .. non_compliant_example:: |
| 66 | + :id: non_compl_ex_UnionBool |
| 67 | + :status: draft |
| 68 | + |
| 69 | + This example reads a boolean from a union field containing an invalid bit pattern. |
| 70 | + The value ``3`` is not a valid boolean (only ``0`` and ``1`` are valid). |
| 71 | + |
| 72 | + .. code-block:: rust |
| 73 | +
|
| 74 | + union IntOrBool { |
| 75 | + i: u8, |
| 76 | + b: bool, |
| 77 | + } |
| 78 | +
|
| 79 | + fn main() { |
| 80 | + let u = IntOrBool { i: 3 }; |
| 81 | + |
| 82 | + // Noncompliant: reading bool field with invalid value (3) |
| 83 | + let invalid_bool = unsafe { u.b }; // UB: 3 is not a valid bool |
| 84 | + } |
| 85 | +
|
| 86 | + .. non_compliant_example:: |
| 87 | + :id: non_compl_ex_UnionChar |
| 88 | + :status: draft |
| 89 | + |
| 90 | + This example reads a ``char`` from a union containing an invalid Unicode value. |
| 91 | + |
| 92 | + .. code-block:: rust |
| 93 | +
|
| 94 | + union IntOrChar { |
| 95 | + i: u32, |
| 96 | + c: char, |
| 97 | + } |
| 98 | +
|
| 99 | + fn main() { |
| 100 | + // 0xD800 is a surrogate, not a valid Unicode scalar value |
| 101 | + let u = IntOrChar { i: 0xD800 }; |
| 102 | + |
| 103 | + // Non-compliant: reading char field with invalid Unicode value |
| 104 | + let invalid_char = unsafe { u.c }; // UB: surrogates are not valid chars |
| 105 | + } |
| 106 | +
|
| 107 | + .. non_compliant_example:: |
| 108 | + :id: non_compl_ex_UnionEnum |
| 109 | + :status: draft |
| 110 | + |
| 111 | + This example reads an enum from a ``union`` containing an invalid discriminant. |
| 112 | + |
| 113 | + .. code-block:: rust |
| 114 | +
|
| 115 | + #[repr(u8)] |
| 116 | + enum Color { |
| 117 | + Red = 0, |
| 118 | + Green = 1, |
| 119 | + Blue = 2, |
| 120 | + } |
| 121 | +
|
| 122 | + union IntOrColor { |
| 123 | + i: u8, |
| 124 | + c: Color, |
| 125 | + } |
| 126 | +
|
| 127 | + fn main() { |
| 128 | + let u = IntOrColor { i: 42 }; |
| 129 | + |
| 130 | + // Noncompliant: 42 is not a valid Color discriminant |
| 131 | + let invalid_color = unsafe { u.c }; // UB: no Color variant for 42 |
| 132 | + } |
| 133 | +
|
| 134 | + .. non_compliant_example:: |
| 135 | + :id: non_compl_ex_UnionRef |
| 136 | + :status: draft |
| 137 | + |
| 138 | + This example reads a reference from a ``union`` containing a null or misaligned pointer. |
| 139 | + |
| 140 | + .. code-block:: rust |
| 141 | +
|
| 142 | + union PtrOrRef { |
| 143 | + p: *const i32, |
| 144 | + r: &'static i32, |
| 145 | + } |
| 146 | +
|
| 147 | + fn main() { |
| 148 | + let u = PtrOrRef { p: std::ptr::null() }; |
| 149 | + |
| 150 | + // Non-compliant: null is not a valid reference |
| 151 | + let invalid_ref = unsafe { u.r }; // UB: references cannot be null |
| 152 | + } |
| 153 | +
|
| 154 | + .. compliant_example:: |
| 155 | + :id: compl_ex_UnionTrackField |
| 156 | + :status: draft |
| 157 | + |
| 158 | + Track the active field explicitly to ensure valid reads. |
| 159 | + |
| 160 | + .. code-block:: rust |
| 161 | +
|
| 162 | + union IntOrBool { |
| 163 | + i: u8, |
| 164 | + b: bool, |
| 165 | + } |
| 166 | +
|
| 167 | + enum ActiveField { |
| 168 | + Int, |
| 169 | + Bool, |
| 170 | + } |
| 171 | +
|
| 172 | + struct SafeUnion { |
| 173 | + data: IntOrBool, |
| 174 | + active: ActiveField, |
| 175 | + } |
| 176 | +
|
| 177 | + impl SafeUnion { |
| 178 | + fn new_int(value: u8) -> Self { |
| 179 | + Self { |
| 180 | + data: IntOrBool { i: value }, |
| 181 | + active: ActiveField::Int, |
| 182 | + } |
| 183 | + } |
| 184 | +
|
| 185 | + fn new_bool(value: bool) -> Self { |
| 186 | + Self { |
| 187 | + data: IntOrBool { b: value }, |
| 188 | + active: ActiveField::Bool, |
| 189 | + } |
| 190 | + } |
| 191 | +
|
| 192 | + fn get_bool(&self) -> Option<bool> { |
| 193 | + match self.active { |
| 194 | + // Compliant: only read bool when we know it was written as bool |
| 195 | + ActiveField::Bool => Some(unsafe { self.data.b }), |
| 196 | + ActiveField::Int => None, |
| 197 | + } |
| 198 | + } |
| 199 | + } |
| 200 | +
|
| 201 | + .. compliant_example:: |
| 202 | + :id: compl_ex_UnionSameField |
| 203 | + :status: draft |
| 204 | + |
| 205 | + Read from the same field that was written. |
| 206 | + |
| 207 | + .. code-block:: rust |
| 208 | +
|
| 209 | + union IntOrBool { |
| 210 | + i: u8, |
| 211 | + b: bool, |
| 212 | + } |
| 213 | +
|
| 214 | + fn main() { |
| 215 | + let u = IntOrBool { b: true }; |
| 216 | + |
| 217 | + // Compliant: reading the same field that was written |
| 218 | + let valid_bool = unsafe { u.b }; |
| 219 | + println!("bool value: {}", valid_bool); |
| 220 | + } |
| 221 | +
|
| 222 | + .. compliant_example:: |
| 223 | + :id: compl_ex_UnionValidReinterpret |
| 224 | + :status: draft |
| 225 | + |
| 226 | + Reinterpret between types where all bit patterns are valid. |
| 227 | + |
| 228 | + .. code-block:: rust |
| 229 | +
|
| 230 | + union IntBytes { |
| 231 | + i: u32, |
| 232 | + bytes: [u8; 4], |
| 233 | + } |
| 234 | +
|
| 235 | + fn main() { |
| 236 | + let u = IntBytes { i: 0x12345678 }; |
| 237 | + |
| 238 | + // Compliant: all bit patterns are valid for [u8; 4] |
| 239 | + let bytes = unsafe { u.bytes }; |
| 240 | + println!("bytes: {:?}", bytes); |
| 241 | + |
| 242 | + let u2 = IntBytes { bytes: [0x11, 0x22, 0x33, 0x44] }; |
| 243 | + |
| 244 | + // Compliant: all bit patterns are valid for u32 |
| 245 | + let int_value = unsafe { u2.i }; |
| 246 | + println!("integer: 0x{:08X}", int_value); |
| 247 | + } |
| 248 | +
|
| 249 | + .. compliant_example:: |
| 250 | + :id: compl_ex_UnionValidateBool |
| 251 | + :status: draft |
| 252 | + |
| 253 | + Validate bytes before reading as a constrained type. |
| 254 | + |
| 255 | + .. code-block:: rust |
| 256 | +
|
| 257 | + union IntOrBool { |
| 258 | + i: u8, |
| 259 | + b: bool, |
| 260 | + } |
| 261 | +
|
| 262 | + fn try_read_bool(u: &IntOrBool) -> Option<bool> { |
| 263 | + // Read as integer first (always valid for u8) |
| 264 | + let raw = unsafe { u.i }; |
| 265 | + |
| 266 | + // Validate before interpreting as bool |
| 267 | + match raw { |
| 268 | + 0 => Some(false), |
| 269 | + 1 => Some(true), |
| 270 | + _ => None, // Invalid bool value |
| 271 | + } |
| 272 | + } |
| 273 | +
|
| 274 | + fn main() { |
| 275 | + let u1 = IntOrBool { i: 1 }; |
| 276 | + let u2 = IntOrBool { i: 3 }; |
| 277 | + |
| 278 | + // Compliant: validates before reading as bool |
| 279 | + println!("u1 as bool: {:?}", try_read_bool(&u1)); // Some(true) |
| 280 | + println!("u2 as bool: {:?}", try_read_bool(&u2)); // None |
| 281 | + } |
| 282 | +
|
| 283 | + .. bibliography:: |
| 284 | + |
| 285 | + .. [RUST-REF-UNION] The Rust Project Developers. "Rust Reference - Unions." |
| 286 | + https://doc.rust-lang.org/reference/items/unions.html |
| 287 | +
|
| 288 | + .. [RUST-REF-UB] The Rust Project Developers. "Rust Reference - Behavior considered undefined." |
| 289 | + https://doc.rust-lang.org/reference/behavior-considered-undefined.html |
| 290 | +
|
| 291 | + .. [UCG-VALIDITY] Rust Unsafe Code Guidelines Working Group. "Validity and Safety Invariant." |
| 292 | + https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#validity-and-safety-invariant |
| 293 | +
|
| 294 | + .. [RUST-NOMICON-UNION] The Rust Project Developers. "The Rustonomicon - Unions." |
| 295 | + https://doc.rust-lang.org/nomicon/unions.html |
| 296 | +
|
9 | 297 | .. guideline:: Use strong types to differentiate between logically distinct values |
10 | 298 | :id: gui_xztNdXA2oFNC |
11 | 299 | :category: advisory |
|
0 commit comments