Skip to main content

fbl/
confine_array_index.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7//! `confine_array_index()` bounds-checks and sanitizes an array index safely in the presence of
8//! speculative execution information leak bugs such as Spectre V1. `confine_array_index()` always
9//! returns a sanitized index, even in speculative-path execution.
10//!
11//! Callers need to combine `confine_array_index` with a conventional bounds check; the bounds
12//! check will return any necessary errors in the nonspeculative path, `confine_array_index` will
13//! confine indexes in the speculative path.
14//!
15//! # Use
16//! `confine_array_index()` returns `index`, if it is < `size`, or 0 if `index` is >= `size`.
17//!
18//! # Example (may leak table1 contents)
19//! ```rust
20//! fn lookup(index: usize, table1: &[usize], table2: &[i32]) -> i32 {
21//!   if index >= table1.len() {
22//!     return -1;
23//!   }
24//!   let index2 = table1[index];
25//!   table2[index2]
26//! }
27//! ```
28//!
29//! Converted:
30//! ```rust
31//! fn lookup(index: usize, table1: &[usize], table2: &[i32]) -> i32 {
32//!   if index >= table1.len() {
33//!     return -1;
34//!   }
35//!   let safe_index = confine_array_index(index, table1.len());
36//!   let index2 = table1[safe_index];
37//!   table2[index2]
38//! }
39//! ```
40
41/// returns `index` if `index < size`, or `0` if `index >= size`.
42/// Immune to speculative execution information leak bugs such as Spectre V1.
43#[inline]
44pub fn confine_array_index(index: usize, size: usize) -> usize {
45    cfg_select! {
46        // No mitigations defined for RISC-V.
47        //
48        // Miri is not run in security-critical contexts and does not support inline assembly.
49        any(miri, target_arch = "riscv64") => {
50            if index < size { index } else { 0 }
51        }
52        target_arch = "aarch64" => {
53            let safe_index: usize;
54            // SAFETY: CSDB barrier and conditional select are safe to use here to prevent
55            // speculative execution leaks.
56            // See "Cache Speculation Side-channels" whitepaper, section "Software Mitigation".
57            // "The combination of both a conditional select/conditional move and the new barrier are
58            // sufficient to address this problem on ALL Arm implementations..."
59            unsafe {
60                core::arch::asm!(
61                    "cmp {index}, {size}",
62                    "csel {safe_index}, {index}, xzr, lo",
63                    "csdb",
64                    index = in(reg) index,
65                    size = in(reg) size,
66                    safe_index = out(reg) safe_index,
67                    options(nostack, nomem)
68                );
69            }
70            safe_index
71        }
72        target_arch = "x86_64" => {
73            let mut safe_index: usize = 0;
74            // SAFETY: CMOVNZ has data dependency on CMP and is safe to use here to prevent
75            // speculative execution leaks.
76            // See "Software Techniques for Managing Speculation on AMD Processors", Mitigation V1-2.
77            // See "Analyzing potential bounds check bypass vulnerabilities", Revision 002,
78            //   Section 5.2 Bounds clipping
79            unsafe {
80                core::arch::asm!(
81                    "cmp {size}, {index}",
82                    "cmova {safe_index}, {index}",
83                    size = in(reg) size,
84                    index = in(reg) index,
85                    safe_index = inout(reg) safe_index,
86                    options(nostack, nomem)
87                );
88            }
89            safe_index
90        }
91        _ => {
92            compile_error!("Provide implementations of confine_array_indexs for your ARCH here");
93            0 // Needed to satisfy return type
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_confine_array_index() {
104        const LIMIT: usize = 265;
105
106        for i in 0..LIMIT {
107            assert_eq!(i, confine_array_index(i, LIMIT));
108        }
109
110        for i in LIMIT..((LIMIT as f64 * 1.5) as usize) {
111            assert_eq!(0, confine_array_index(i, LIMIT));
112        }
113
114        assert_eq!(0, confine_array_index(usize::MAX, LIMIT));
115        assert_eq!(0, confine_array_index(usize::MAX - 1, 1));
116        assert_eq!(0, confine_array_index(0, 1));
117        assert_eq!(0, confine_array_index(1, 1));
118    }
119}