1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <linux/futex.h> 18#include <pthread.h> 19#include <sys/stat.h> 20#include <unistd.h> 21 22#include <atomic> 23#include <chrono> 24#include <string> 25#include <thread> 26#include <vector> 27 28#include <android-base/file.h> 29#include <android-base/scopeguard.h> 30#include <android-base/test_utils.h> 31#include <gtest/gtest.h> 32#include <selinux/android.h> 33#include <selinux/label.h> 34#include <selinux/selinux.h> 35 36using namespace std::chrono_literals; 37using namespace std::string_literals; 38 39template <typename T, typename F> 40void WriteFromMultipleThreads(std::vector<std::pair<std::string, T>>& files_and_parameters, 41 F function) { 42 auto num_threads = files_and_parameters.size(); 43 pthread_barrier_t barrier; 44 pthread_barrier_init(&barrier, nullptr, num_threads); 45 auto barrier_destroy = 46 android::base::make_scope_guard([&barrier]() { pthread_barrier_destroy(&barrier); }); 47 48 auto make_thread_function = [&function, &barrier](const auto& file, const auto& parameter) { 49 return [&]() { 50 function(parameter); 51 pthread_barrier_wait(&barrier); 52 android::base::WriteStringToFile("<empty>", file); 53 }; 54 }; 55 56 std::vector<std::thread> threads; 57 // TODO(b/63712782): Structured bindings + templated containers are broken in clang :( 58 // for (const auto& [file, parameter] : files_and_parameters) { 59 for (const auto& pair : files_and_parameters) { 60 const auto& file = pair.first; 61 const auto& parameter = pair.second; 62 threads.emplace_back(std::thread(make_thread_function(file, parameter))); 63 } 64 65 for (auto& thread : threads) { 66 thread.join(); 67 } 68} 69 70TEST(ueventd, setegid_IsPerThread) { 71 if (getuid() != 0) { 72 GTEST_LOG_(INFO) << "Skipping test, must be run as root."; 73 return; 74 } 75 76 TemporaryDir dir; 77 78 gid_t gid = 0; 79 std::vector<std::pair<std::string, gid_t>> files_and_gids; 80 std::generate_n(std::back_inserter(files_and_gids), 100, [&gid, &dir]() { 81 gid++; 82 return std::pair(dir.path + "/gid_"s + std::to_string(gid), gid); 83 }); 84 85 WriteFromMultipleThreads(files_and_gids, [](gid_t gid) { EXPECT_EQ(0, setegid(gid)); }); 86 87 for (const auto& [file, expected_gid] : files_and_gids) { 88 struct stat info; 89 ASSERT_EQ(0, stat(file.c_str(), &info)); 90 EXPECT_EQ(expected_gid, info.st_gid); 91 } 92} 93 94TEST(ueventd, setfscreatecon_IsPerThread) { 95 if (getuid() != 0) { 96 GTEST_LOG_(INFO) << "Skipping test, must be run as root."; 97 return; 98 } 99 if (!is_selinux_enabled() || security_getenforce() == 1) { 100 GTEST_LOG_(INFO) << "Skipping test, SELinux must be enabled and in permissive mode."; 101 return; 102 } 103 104 const char* const contexts[] = { 105 "u:object_r:audio_device:s0", 106 "u:object_r:sensors_device:s0", 107 "u:object_r:video_device:s0" 108 "u:object_r:zero_device:s0", 109 }; 110 111 TemporaryDir dir; 112 std::vector<std::pair<std::string, std::string>> files_and_contexts; 113 for (const char* context : contexts) { 114 files_and_contexts.emplace_back(dir.path + "/context_"s + context, context); 115 } 116 117 WriteFromMultipleThreads(files_and_contexts, [](const std::string& context) { 118 EXPECT_EQ(0, setfscreatecon(context.c_str())); 119 }); 120 121 for (const auto& [file, expected_context] : files_and_contexts) { 122 char* file_context; 123 ASSERT_GT(getfilecon(file.c_str(), &file_context), 0); 124 EXPECT_EQ(expected_context, file_context); 125 freecon(file_context); 126 } 127} 128 129TEST(ueventd, selabel_lookup_MultiThreaded) { 130 if (getuid() != 0) { 131 GTEST_LOG_(INFO) << "Skipping test, must be run as root."; 132 return; 133 } 134 135 // Test parameters 136 constexpr auto num_threads = 10; 137 constexpr auto run_time = 200ms; 138 139 std::unique_ptr<selabel_handle, decltype(&selabel_close)> sehandle( 140 selinux_android_file_context_handle(), &selabel_close); 141 142 ASSERT_TRUE(sehandle); 143 144 struct { 145 const char* file; 146 int mode; 147 std::string expected_context; 148 } files_and_modes[] = { 149 {"/dev/zero", 020666, ""}, 150 {"/dev/null", 020666, ""}, 151 {"/dev/random", 020666, ""}, 152 {"/dev/urandom", 020666, ""}, 153 }; 154 155 // Precondition, ensure that we can lookup all of these from a single thread, and store the 156 // expected context for each. 157 for (size_t i = 0; i < arraysize(files_and_modes); ++i) { 158 char* secontext; 159 ASSERT_EQ(0, selabel_lookup(sehandle.get(), &secontext, files_and_modes[i].file, 160 files_and_modes[i].mode)); 161 files_and_modes[i].expected_context = secontext; 162 freecon(secontext); 163 } 164 165 // Now that we know we can access them, and what their context should be, run in parallel. 166 std::atomic_bool stopped = false; 167 std::atomic_uint num_api_failures = 0; 168 std::atomic_uint num_context_check_failures = 0; 169 std::atomic_uint num_successes = 0; 170 171 auto thread_function = [&]() { 172 while (!stopped) { 173 for (size_t i = 0; i < arraysize(files_and_modes); ++i) { 174 char* secontext; 175 int result = selabel_lookup(sehandle.get(), &secontext, files_and_modes[i].file, 176 files_and_modes[i].mode); 177 if (result != 0) { 178 num_api_failures++; 179 } else { 180 if (files_and_modes[i].expected_context != secontext) { 181 num_context_check_failures++; 182 } else { 183 num_successes++; 184 } 185 freecon(secontext); 186 } 187 } 188 } 189 }; 190 191 std::vector<std::thread> threads; 192 std::generate_n(back_inserter(threads), num_threads, 193 [&]() { return std::thread(thread_function); }); 194 195 std::this_thread::sleep_for(run_time); 196 stopped = true; 197 for (auto& thread : threads) { 198 thread.join(); 199 } 200 201 EXPECT_EQ(0U, num_api_failures); 202 EXPECT_EQ(0U, num_context_check_failures); 203 EXPECT_GT(num_successes, 0U); 204} 205