blob: 9b63604a249e3100e69ddf8dbb5df31aa0719118 [file] [log] [blame]
Janis Danisevskisd746a0d2021-05-14 10:58:26 -07001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use keystore2_selinux::{check_access, Context};
16use nix::sched::sched_setaffinity;
17use nix::sched::CpuSet;
18use nix::unistd::getpid;
19use std::cmp::min;
20use std::thread;
21use std::{
22 sync::{atomic::AtomicU8, atomic::Ordering, Arc},
23 time::Duration,
24};
25
26#[derive(Clone, Copy)]
27struct CatCount(u8, u8, u8, u8);
28
29impl CatCount {
30 fn next(&mut self) -> CatCount {
31 let result = *self;
32 if self.3 == 255 {
33 if self.2 == 254 {
34 if self.1 == 253 {
35 if self.0 == 252 {
36 self.0 = 255;
37 }
38 self.0 += 1;
39 self.1 = self.0;
40 }
41 self.1 += 1;
42 self.2 = self.1;
43 }
44 self.2 += 1;
45 self.3 = self.2;
46 }
47 self.3 += 1;
48 result
49 }
50
51 fn make_string(&self) -> String {
52 format!("c{},c{},c{},c{}", self.0, self.1, self.2, self.3)
53 }
54}
55
56impl Default for CatCount {
57 fn default() -> Self {
58 Self(0, 1, 2, 3)
59 }
60}
61
62/// This test calls selinux_check_access concurrently causing access vector cache misses
63/// in libselinux avc. The test then checks if any of the threads fails to report back
64/// after a burst of access checks. The purpose of the test is to draw out a specific
65/// access vector cache corruption that sends a calling thread into an infinite loop.
66/// This was observed when keystore2 used libselinux concurrently in a non thread safe
67/// way. See b/184006658.
68#[test]
69fn test_concurrent_check_access() {
70 android_logger::init_once(
71 android_logger::Config::default()
72 .with_tag("keystore2_selinux_concurrency_test")
73 .with_min_level(log::Level::Debug),
74 );
75
76 static AVS: &[(&str, &str, &str, &str)] = &[
77 ("u:object_r:keystore:s0", "u:r:untrusted_app:s0:c0,c1,c2,c3", "use", "keystore2_key"),
78 ("u:object_r:su_key:s0", "u:r:su:s0", "get_info", "keystore2_key"),
79 ("u:object_r:shell_key:s0", "u:r:shell:s0", "delete", "keystore2_key"),
80 ("u:object_r:keystore:s0", "u:r:system_server:s0", "add_auth", "keystore2"),
81 ("u:object_r:keystore:s0", "u:r:su:s0", "change_user", "keystore2"),
82 (
83 "u:object_r:vold_key:s0",
84 "u:r:vold:s0",
85 "convert_storage_key_to_ephemeral",
86 "keystore2_key",
87 ),
88 ];
89
90 let cpus = num_cpus::get();
91
92 let turnpike = Arc::new(AtomicU8::new(0));
93
94 let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
95
96 let goals: Arc<Vec<AtomicU8>> = Arc::new((0..cpus).map(|_| AtomicU8::new(0)).collect());
97
98 let turnpike_clone = turnpike.clone();
99 let goals_clone = goals.clone();
100
101 threads.push(thread::spawn(move || {
102 let mut cpu_set = CpuSet::new();
103 cpu_set.set(0).unwrap();
104 sched_setaffinity(getpid(), &cpu_set).unwrap();
105 let mut cat_count: CatCount = Default::default();
106
107 log::info!("Thread 0 reached turnpike");
108 loop {
109 turnpike_clone.fetch_add(1, Ordering::Relaxed);
110 loop {
111 match turnpike_clone.load(Ordering::Relaxed) {
112 0 => break,
113 255 => {
114 goals_clone[0].store(1, Ordering::Relaxed);
115 return;
116 }
117 _ => {}
118 }
119 }
120
121 for _ in 0..100 {
122 let (tctx, sctx, perm, class) = (
123 Context::new("u:object_r:keystore:s0").unwrap(),
124 Context::new(&format!(
125 "u:r:untrusted_app:s0:{}",
126 cat_count.next().make_string()
127 ))
128 .unwrap(),
129 "use",
130 "keystore2_key",
131 );
132
133 check_access(&sctx, &tctx, class, perm).unwrap();
134 }
135
136 goals_clone[0].store(1, Ordering::Relaxed);
137 }
138 }));
139
140 for i in 1..cpus {
141 let turnpike_clone = turnpike.clone();
142 let goals_clone = goals.clone();
143
144 log::info!("Spawning thread {}", i);
145 threads.push(thread::spawn(move || {
146 let mut cpu_set = CpuSet::new();
147 cpu_set.set(i).unwrap();
148 sched_setaffinity(getpid(), &cpu_set).unwrap();
149
150 let (tctx, sctx, perm, class) = AVS[i % min(cpus, AVS.len())];
151
152 let sctx = Context::new(sctx).unwrap();
153 let tctx = Context::new(tctx).unwrap();
154
155 log::info!("Thread {} reached turnpike", i);
156 loop {
157 turnpike_clone.fetch_add(1, Ordering::Relaxed);
158 loop {
159 match turnpike_clone.load(Ordering::Relaxed) {
160 0 => break,
161 255 => {
162 goals_clone[i].store(1, Ordering::Relaxed);
163 return;
164 }
165 _ => {}
166 }
167 }
168
169 for _ in 0..100 {
170 check_access(&sctx, &tctx, class, perm).unwrap();
171 }
172
173 goals_clone[i].store(1, Ordering::Relaxed);
174 }
175 }));
176 }
177
178 let mut i = 0;
179
180 loop {
181 thread::sleep(Duration::from_millis(200));
182
183 // Give the threads some time to reach and spin on the turn pike.
184 assert_eq!(turnpike.load(Ordering::Relaxed) as usize, cpus, "i = {}", i);
185 if i == 100 {
186 turnpike.store(255, Ordering::Relaxed);
187 break;
188 }
189 // Now go.
190 turnpike.store(0, Ordering::Relaxed);
191 i += 1;
192 }
193
194 for t in threads {
195 t.join().unwrap();
196 }
197}