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}