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