+++ /dev/null
-/*
- * Copyright (c) 2016-2023 Samsung Electronics Co., Ltd. All rights reserved.
- *
- * This file is licensed under the terms of MIT License or the Apache License
- * Version 2.0 of your choice. See the LICENSE.MIT file for MIT license details.
- * See the LICENSE file or the notice below for Apache License Version 2.0
- * details.
- *
- * 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.
- */
-
-/*
- * @brief Implementation of proper privilege dropping check utilities, client side
- */
-
-#include <string>
-
-#include <check-proper-drop.h>
-#include <dpl/exception.h>
-#include <dpl/log/log.h>
-#include <filesystem.h>
-
-namespace SecurityManager {
-namespace CheckProperDrop {
-
-namespace {
-
-DECLARE_EXCEPTION_TYPE(SecurityManager::Exception, DropError)
-
-} // namespace
-
-std::unordered_set<int> checkThreads(int currentTid)
-{
- FS::FileNameVector current_tids;
- for (unsigned i = 0; i < 10; ++i) {
- try {
- current_tids = FS::getSubDirectoriesFromDirectory("/proc/self/task", true);
- break;
- } catch(const FS::Exception::FileError&) {
- if (i == 9)
- throw;
-
- /*
- * This may happen if a thread disappears, an entry is removed from /proc and fstatat
- * fails.
- */
- LogWarning("Reading /proc/self/task/ failed. Retrying.");
- }
- }
-
- std::unordered_set<int> result;
- for (auto &tid_s : current_tids) {
- int tid = atoi(tid_s.c_str());
-
- if (tid == currentTid)
- continue;
-
- LogDebug("Thread " << tid_s << " new");
- result.emplace(tid);
- }
- return result;
-}
-
-} // namespace CheckProperDrop
-} // namespace SecurityManager
static std::atomic<int> g_thread_state[MAX_THREAD_COUNT];
static std::atomic<int> g_thread_index[MAX_THREAD_COUNT];
static std::atomic<int> g_managed_tids_num;
+static bool g_thread_alive[MAX_THREAD_COUNT];
+static bool g_thread_signaled[MAX_THREAD_COUNT];
static cap_t g_cap;
static inline void add_managed_tid(int tid){
}
g_thread_state[g_managed_tids_num] = 0;
g_thread_index[g_managed_tids_num] = tid;
+ g_thread_alive[g_managed_tids_num] = true;
g_managed_tids_num++;
}
g_thread_state[index] = state;
}
-static int count_alive_tids_with_state(int status, const std::unordered_set<int>& alive_tids) {
- int count = 0;
- for (int i = 0; i < g_managed_tids_num; ++i)
- if (g_thread_state[i] == status && alive_tids.find(g_thread_index[i]) != alive_tids.end())
- ++ count;
- return count;
-}
-
SECURITY_MANAGER_API
const char *security_manager_strerror(enum lib_retcode rc)
{
cap_free(my_caps);
}
+static bool alive_threads_have_state(int status) noexcept
+{
+ for (int i = 0; i < g_managed_tids_num; ++i) {
+ if (!g_thread_alive[i])
+ continue;
+
+ if (g_thread_state[i] != status)
+ return false;
+ }
+ return true;
+}
+
+static int get_alive_threads(int own_tid) noexcept
+{
+ // reset alive status
+ memset(&g_thread_alive, 0, sizeof(g_thread_alive));
+
+ auto dir_fd = open("/proc/self/task", O_DIRECTORY | O_CLOEXEC);
+ if (dir_fd == -1)
+ return errno;
+
+ char buf[1024];
+ struct dirent64 *d;
+ int bpos = 0;
+ for(;;) {
+ ssize_t nread = getdents64(dir_fd, buf, sizeof(buf));
+ if (nread == -1) {
+ close(dir_fd);
+ return errno;
+ }
+
+ if (nread == 0)
+ break;
+
+ for (bpos = 0; bpos < nread; bpos += d->d_reclen) {
+ d = (struct dirent64 *) (buf + bpos);
+
+ // TODO it's DT_UNKNOWN for some reason
+// d_type = *(buf + bpos + d->d_reclen - 1);
+// if (d_type != DT_DIR)
+// continue;
+
+ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
+ continue;
+
+ char* endptr;
+
+ errno = 0;
+ long tid = strtol(d->d_name, &endptr, 10);
+ if (errno != 0) {
+ close(dir_fd);
+ return errno;
+ }
+
+ if (tid == own_tid)
+ continue;
+
+ // Mark as alive. Append new if necessary.
+ int i = 0;
+ for (; i < g_managed_tids_num; ++i) {
+ if (g_thread_index[i] == tid) {
+ g_thread_alive[i] = true;
+ break;
+ }
+ }
+ if (i != g_managed_tids_num)
+ continue;
+
+ add_managed_tid(tid);
+ }
+ }
+
+ close(dir_fd);
+ return 0;
+}
+
+static int check_threads(int own_tid) noexcept
+{
+ for (unsigned i = 0; i < 10; ++i) {
+ auto ret = get_alive_threads(own_tid);
+ if (ret == 0)
+ break;
+
+ /*
+ * This may happen if a thread disappears, an entry is removed from /proc and fstatat
+ * fails.
+ */
+ if (i == 9)
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool no_new_threads() noexcept
+{
+ for (int i = 0; i < g_managed_tids_num; ++i) {
+ if (!g_thread_alive[i])
+ continue;
+
+ if (!g_thread_signaled[i])
+ return false;
+ }
+ return true;
+}
+
+static int signal_and_wait_for_handlers(pid_t own_pid, int own_tid) noexcept
+{
+ // No allocations allowed in this function
+ int ret = 0;
+ int time_left = MAX_SIG_WAIT_TIME;
+ do {
+ ret = check_threads(own_tid);
+ if (ret != 0)
+ break;
+
+ for (int i = 0; i < g_managed_tids_num; ++i) {
+ if (!g_thread_alive[i])
+ break;
+
+ if (!g_thread_signaled[i]) {
+ g_thread_signaled[i] = true;
+ int tid = g_thread_index[i];
+
+ // thread not signaled yet, send signal
+ if (Syscall::tgkill(own_pid, tid, SIGSETXID) < 0) {
+ auto err = errno;
+ if (EAGAIN == err) { // resource temporarily unavailable, trying again
+ usleep(SLEEP_CONST); // 10 ms
+ if (Syscall::tgkill(own_pid, tid, SIGSETXID) < 0) {
+ err = errno;
+ if (ESRCH == err) { // thread already gone - noop
+ continue;
+ } else {
+ return err;
+ }
+ }
+ } else if (ESRCH == err) { // thread already gone - noop
+ continue;
+ } else {
+ return err;
+ }
+ }
+ }
+ }
+
+ usleep(SLEEP_CONST); // 10 ms
+ --time_left;
+
+ // break if number of threads in waiting state equals to number of alive tids minus current one
+ if (alive_threads_have_state(1)) {
+ // threads that were read some lines above are all sleeping here - BUT - could have
+ // spawned new threads between reading list from /proc and checking status with count_alive_tids_with_state()
+ // to make sure the loop can end, here we read /proc again
+ ret = check_threads(own_tid);
+ if (ret != 0)
+ break;
+
+ if (no_new_threads())
+ // here indeed, no new threads are present and they are all waiting in the signal handler
+ break;
+ }
+
+ if (time_left == 0)
+ return ETIME;
+ } while (1);
+ return ret;
+}
+
static inline int security_manager_sync_threads_internal(const std::string &app_label)
{
static_assert(ATOMIC_INT_LOCK_FREE == 2, "std::atomic<int> is not always lock free");
int own_tid = Syscall::gettid();
const pid_t own_pid = getpid();
- std::unordered_set<int> tids_sent_signals = {};
-
g_cap = cap_init();
if (!g_cap) {
std::atomic_thread_fence(std::memory_order_release);
g_th_barrier = 0;
- int time_left = MAX_SIG_WAIT_TIME;
- std::unordered_set<int> current_tids = {};
- do {
- current_tids = CheckProperDrop::checkThreads(own_tid);
- for (auto existing_tid : current_tids) {
- if (tids_sent_signals.find(existing_tid) == tids_sent_signals.end()) {
- tids_sent_signals.insert(existing_tid);
- add_managed_tid(existing_tid);
- // thread not managed yet, send signal
- if (Syscall::tgkill(own_pid, existing_tid, SIGSETXID) < 0) {
- auto err = errno;
- if (EAGAIN == err) { // resource temporarily unavailable, trying again
- LogWarning("Received EAGAIN from tgkill, wait a bit & try again");
- usleep(SLEEP_CONST); // 10 ms
- if (Syscall::tgkill(own_pid, existing_tid, SIGSETXID) < 0) {
- err = errno;
- if (ESRCH == err) { // thread already gone - noop
- continue;
- } else {
- LogWithErrno(err, "tgkill()");
- abort();
- }
- }
- } else if (ESRCH == err) { // thread already gone - noop
- continue;
- } else {
- LogWithErrno(err, "tgkill()");
- abort();
- }
- }
- }
- }
-
- usleep(SLEEP_CONST); // 10 ms
- --time_left;
-
- // break if number of threads in waiting state equals to number of alive tids minus current one
- int ready_tids = count_alive_tids_with_state(1, current_tids);
- if (ready_tids == (int)current_tids.size()) {
- // threads that were read some lines above are all sleeping here - BUT - could have
- // spawned new threads between reading list from /proc and checking status with count_alive_tids_with_state()
- // to make sure the loop can end, here we read /proc again
- if(CheckProperDrop::checkThreads(own_tid) == current_tids)
- // here indeed, no new threads are present and they are all waiting in the signal handler
- break;
- }
-
- if (time_left % 500 == 0)
- LogWarning("Still waiting for threads to receive signals... signals received by: " << ready_tids
- << ", all tids alive now without current: "
- << current_tids.size());
- if (time_left == 0) {
- LogError("Too much waiting for receiving signals, aborting! Signals received by: " << ready_tids
- << ", out of alive tids without current: "
- << current_tids.size());
- abort();
- }
- } while (1);
+ int ret = signal_and_wait_for_handlers(own_pid, own_tid);
+ if (ret != 0) {
+ LogError("Error occured during signaling: " << ret);
+ abort();
+ }
// here, all TIDs except current one are waiting to start changing attributes
// We can assume these TIDs will continue to live (no need to read /proc again), since no logic
g_th_barrier++; // this starts the signal handlers - they will proceed once they wake up
- int ready_tids = 0;
-
+ bool ready = false;
for (int i = MAX_SIG_WAIT_TIME; i > 0; --i) {
// break if number of completed tids equals to all of them minus current one!
- ready_tids = count_alive_tids_with_state(2, current_tids);
- if (ready_tids == (int)current_tids.size())
+
+ ready = alive_threads_have_state(2);
+ if (ready)
break;
usleep(SLEEP_CONST); // 10 ms
if (i % 500 == 0)
- LogWarning("Still waiting for threads to finalize handlers... completed by: " << ready_tids
- << ", all tids managed now: "
- << current_tids.size());
+ LogWarning("Still waiting for threads to finalize handlers.");
}
- if (ready_tids != (int)current_tids.size()) {
+ if (!ready) {
// not all TIDs reached this stage, aborting!
- LogError("Too much waiting for sig handler completion, aborting! Signals completed by: " << ready_tids
- << ", all tids managed now: "
- << current_tids.size());
+ LogError("Too much waiting for sig handler completion, aborting!");
abort();
}