blob: a5d2df2c4ac135966478070510613a4169ee72a7 [file] [log] [blame]
// Copyright 2021, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use keystore2_selinux::{check_access, Context};
use nix::sched::sched_setaffinity;
use nix::sched::CpuSet;
use nix::unistd::getpid;
use std::thread;
use std::{
sync::{atomic::AtomicU8, atomic::Ordering, Arc},
time::{Duration, Instant},
};
#[derive(Clone, Copy)]
struct CatCount(u8, u8, u8, u8);
impl CatCount {
fn next(&mut self) -> CatCount {
let result = *self;
if self.3 == 255 {
if self.2 == 254 {
if self.1 == 253 {
if self.0 == 252 {
self.0 = 255;
}
self.0 += 1;
self.1 = self.0;
}
self.1 += 1;
self.2 = self.1;
}
self.2 += 1;
self.3 = self.2;
}
self.3 += 1;
result
}
fn make_string(&self) -> String {
format!("c{},c{},c{},c{}", self.0, self.1, self.2, self.3)
}
}
impl Default for CatCount {
fn default() -> Self {
Self(0, 1, 2, 3)
}
}
/// This test calls selinux_check_access concurrently causing access vector cache misses
/// in libselinux avc. The test then checks if any of the threads fails to report back
/// after a burst of access checks. The purpose of the test is to draw out a specific
/// access vector cache corruption that sends a calling thread into an infinite loop.
/// This was observed when keystore2 used libselinux concurrently in a non thread safe
/// way. See b/184006658.
#[test]
fn test_concurrent_check_access() {
android_logger::init_once(
android_logger::Config::default()
.with_tag("keystore2_selinux_concurrency_test")
.with_min_level(log::Level::Debug),
);
let cpus = num_cpus::get();
let turnpike = Arc::new(AtomicU8::new(0));
let complete_count = Arc::new(AtomicU8::new(0));
let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
for i in 0..cpus {
log::info!("Spawning thread {}", i);
let turnpike_clone = turnpike.clone();
let complete_count_clone = complete_count.clone();
threads.push(thread::spawn(move || {
let mut cpu_set = CpuSet::new();
cpu_set.set(i).unwrap();
sched_setaffinity(getpid(), &cpu_set).unwrap();
let mut cat_count: CatCount = Default::default();
log::info!("Thread 0 reached turnpike");
loop {
turnpike_clone.fetch_add(1, Ordering::Relaxed);
loop {
match turnpike_clone.load(Ordering::Relaxed) {
0 => break,
255 => return,
_ => {}
}
}
for _ in 0..250 {
let (tctx, sctx, perm, class) = (
Context::new("u:object_r:keystore:s0").unwrap(),
Context::new(&format!(
"u:r:untrusted_app:s0:{}",
cat_count.next().make_string()
))
.unwrap(),
"use",
"keystore2_key",
);
check_access(&sctx, &tctx, class, perm).unwrap();
}
complete_count_clone.fetch_add(1, Ordering::Relaxed);
while complete_count_clone.load(Ordering::Relaxed) as usize != cpus {
thread::sleep(Duration::from_millis(5));
}
}
}));
}
let mut i = 0;
let run_time = Instant::now();
loop {
const TEST_ITERATIONS: u32 = 500;
const MAX_SLEEPS: u64 = 500;
const SLEEP_MILLISECONDS: u64 = 5;
let mut sleep_count: u64 = 0;
while turnpike.load(Ordering::Relaxed) as usize != cpus {
thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS));
sleep_count += 1;
assert!(
sleep_count < MAX_SLEEPS,
"Waited too long to go ready on iteration {}, only {} are ready",
i,
turnpike.load(Ordering::Relaxed)
);
}
if i % 100 == 0 {
let elapsed = run_time.elapsed().as_secs();
println!("{:02}:{:02}: Iteration {}", elapsed / 60, elapsed % 60, i);
}
// Give the threads some time to reach and spin on the turn pike.
assert_eq!(turnpike.load(Ordering::Relaxed) as usize, cpus, "i = {}", i);
if i >= TEST_ITERATIONS {
turnpike.store(255, Ordering::Relaxed);
break;
}
// Now go.
complete_count.store(0, Ordering::Relaxed);
turnpike.store(0, Ordering::Relaxed);
i += 1;
// Wait for them to all complete.
sleep_count = 0;
while complete_count.load(Ordering::Relaxed) as usize != cpus {
thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS));
sleep_count += 1;
if sleep_count >= MAX_SLEEPS {
// Enable the following block to park the thread to allow attaching a debugger.
if false {
println!(
"Waited {} seconds and we seem stuck. Going to sleep forever.",
(MAX_SLEEPS * SLEEP_MILLISECONDS) as f32 / 1000.0
);
loop {
thread::park();
}
} else {
assert!(
sleep_count < MAX_SLEEPS,
"Waited too long to complete on iteration {}, only {} are complete",
i,
complete_count.load(Ordering::Relaxed)
);
}
}
}
}
for t in threads {
t.join().unwrap();
}
}