Change the way threads are checked at the end of prepare_app call 03/294203/9
authorTomasz Swierczek <t.swierczek@samsung.com>
Wed, 14 Jun 2023 07:45:05 +0000 (09:45 +0200)
committerTomasz Swierczek <t.swierczek@samsung.com>
Tue, 20 Jun 2023 04:43:56 +0000 (06:43 +0200)
It was verified experimentally(*), that even if functions:

(1) smack_set_label_for_self()
(2) cap_set_proc()

have successfully changed process (main thread) security attributes,
even then /proc can contain stale/older data when implementation
in file:

check-proper-drop.cpp (the client side)

is checking whether all threads are properly synchronized.

Assuming mentioned functions operate properly and we trust their
return values, we can assume that checking in check-proper-drop.cpp
can be limited to checking if any new thread was spawned that was not
on the list of threads during call to function:

security_manager_sync_threads_internal()

This way, we're not relying on contents of /proc in terms of actual
security attributes, just the list of thread-IDs.

(*)

Following error (abort in client library) has been detected during
automated testing of VD image:

19548.940 E/SECURITY_MANAGER_CLIENT(P11930, T11930): check-proper-drop.cpp: checkThreads(175) > Offending taskId is: 11930

which means, that even though (1) and (2) succeeded for current taskId
(these had to, analysing the logic of our client library), there was an issue
in checking the contents of /proc for current taskId.

Change-Id: Ida49acd8981eea8c3df30ef32f23a1f4b0ef1ae3

src/client/check-proper-drop.cpp
src/client/client-security-manager.cpp
src/common/include/check-proper-drop.h
test/CMakeLists.txt
test/test_check_proper_drop.cpp [deleted file]

index 2d7343f..d8baeae 100644 (file)
  * @brief Implementation of proper privilege dropping check utilities, client side
  */
 
-#include <cassert>
-#include <cctype>
-#include <fstream>
 #include <string>
-#include <sys/stat.h>
+#include <vector>
 
 #include <check-proper-drop.h>
 #include <dpl/exception.h>
 #include <dpl/log/log.h>
 #include <filesystem.h>
-#include <smack-labels.h>
 
 namespace SecurityManager {
 namespace CheckProperDrop {
@@ -42,139 +38,24 @@ namespace {
 
 DECLARE_EXCEPTION_TYPE(SecurityManager::Exception, DropError)
 
-class TaskData final {
-    ::std::string m_label;
-    ::std::string m_uid, m_gid, m_groups;
-    ::std::string m_capInh, m_capPrm, m_capEff;
-    ino_t m_nsIno[N_FLAG_BITS] = {};
-
-    // Uid Gid Groups CapInh CapPrm CapEff
-    static constexpr int N_WANTED_STATUS_LINES = 6;
-
-    static bool fillIfHasId(::std::string &lval, const ::std::string &rval, const char *id)
-    {
-        if (rval.rfind(id, 0) != 0)
-            return false;
-        if (!lval.empty())
-            ThrowMsg(DropError, "ambiguous lines (" << lval << "), (" << rval << ')');
-        lval = rval;
-        return true;
-    }
-
-    bool fillStatusLine(const std::string &line)
-    {
-        return fillIfHasId(m_uid,    line, "Uid:")
-            || fillIfHasId(m_gid,    line, "Gid:")
-            || fillIfHasId(m_groups, line, "Groups:")
-            || fillIfHasId(m_capInh, line, "CapInh:")
-            || fillIfHasId(m_capPrm, line, "CapPrm:")
-            || fillIfHasId(m_capEff, line, "CapEff:");
-    }
-
-    void fillStatus(const ::std::string &taskRoot)
-    {
-        ::std::string statusPath = taskRoot + "status";
-        ::std::ifstream stream(statusPath);
-
-        size_t linesFilled = 0;
-        for (::std::string line; ::std::getline(stream, line);)
-            if (fillStatusLine(line))
-                linesFilled++;
-        if (linesFilled != N_WANTED_STATUS_LINES)
-            ThrowMsg(DropError, '(' << statusPath << ") missing required information");
-    }
-
-    void fillNs(const ::std::string &taskRoot, BitFlags checkProperDropFlags)
-    {
-        for (size_t nsNameIdx = 0; nsNameIdx < N_FLAG_BITS; nsNameIdx++) {
-            if (!(checkProperDropFlags & (1 << nsNameIdx)))
-                continue;
-
-            ::std::string nsPath = taskRoot + "ns/" + NS_NAMES[nsNameIdx];
-            struct ::stat st;
-            if (::stat(nsPath.c_str(), &st) != 0) {
-                int err = errno;
-                ThrowMsg(DropError, "stat failed (" << nsPath << ") errno=" << err);
-            }
-
-            m_nsIno[nsNameIdx] = st.st_ino;
-        }
-    }
-
-    static void checkCaps(const ::std::string &capsLine)
-    {
-        assert(!::isspace(capsLine.back()));
-        auto pos = capsLine.find_last_not_of("0");
-        assert(pos != ::std::string::npos);
-        if (!::isspace(capsLine[pos]) && capsLine[pos] != ':')
-            ThrowMsg(DropError, "caps not zero (" << capsLine << ')');
-    }
-
-    template <class T>
-    static void checkSame(const T &a, const T &b, const ::std::string &what)
-    {
-        if (a != b)
-            ThrowMsg(DropError, "tasks differ [" << what << "]: (" << a << ") != (" << b << ")");
-    }
-
-public:
-    TaskData() {}
-
-    void initialize(const ::std::string &taskId, BitFlags checkProperDropFlags) {
-        m_label = SmackLabels::getSmackLabelFromPid(::std::stoul(taskId));
-        ::std::string taskRoot = "/proc/self/task/" + taskId + "/";
-        fillStatus(taskRoot);
-        fillNs(taskRoot, checkProperDropFlags);
-
-        checkCaps(m_capInh);
-        checkCaps(m_capPrm);
-        checkCaps(m_capEff);
-    }
-
-
-    TaskData(const ::std::string &taskId, BitFlags checkProperDropFlags)
-    {
-        initialize(taskId, checkProperDropFlags);
-    }
-
-
-    void checkSameAs(const TaskData &other) const {
-        checkSame(m_label, other.m_label, "label");
-        checkSame(m_uid, other.m_uid, "uid");
-        checkSame(m_gid, other.m_gid, "gid");
-        checkSame(m_groups, other.m_groups, "groups");
-        for (size_t nsNameIdx = 0; nsNameIdx < N_FLAG_BITS; nsNameIdx++)
-            checkSame(m_nsIno[nsNameIdx], other.m_nsIno[nsNameIdx], NS_NAMES[nsNameIdx]);
-    }
-};
-
 }  // namespace
 
-void checkThreads(BitFlags checkProperDropFlags)
+void checkThreads(const std::vector<int> &synced_tids)
 {
-    LogDebug("checkProperDrop flags (" << static_cast<unsigned>(checkProperDropFlags) << ')');
-
-    auto taskIds = FS::getSubDirectoriesFromDirectory("/proc/self/task", true);
-    if (taskIds.empty())
-        ThrowMsg(DropError, "no tasks found");
-
-    TaskData lastTaskData;
-    try {
-        lastTaskData.initialize(taskIds.back(), checkProperDropFlags);
-    } catch (...) {
-        LogError("Offending taskId is: " << taskIds.back());
-        throw;
-    }
-
-    taskIds.pop_back();
-
-    for (const auto &taskId : taskIds)
-        try {
-            TaskData(taskId, checkProperDropFlags).checkSameAs(lastTaskData);
-        } catch (...) {
-            LogError("Offending taskId is: " << taskId);
-            throw;
+    auto current_tids = FS::getSubDirectoriesFromDirectory("/proc/self/task", true);
+    for (auto &tid_s : current_tids) {
+        int tid = atoi(tid_s.c_str());
+        bool found = false;
+        for (auto &synced_tid : synced_tids)
+            if (synced_tid == tid) {
+                found = true;
+                break;
+            }
+        if (!found) {
+            LogError("New thread found that was not synchronized, must have not been dropped! TID: " << tid);
+            ThrowMsg(DropError, "New thread found that was not synchronized, must have not been dropped! TID: " << tid);
         }
+    }
 }
 
 }  // namespace CheckProperDrop
index 697c1e9..9df9d91 100644 (file)
@@ -591,7 +591,7 @@ inline static int label_for_self_internal()
     return ret < 0 ? -1 : 0;
 }
 
-static inline int security_manager_sync_threads_internal(const std::string &app_label)
+static inline int security_manager_sync_threads_internal(const std::string &app_label, std::vector<int> &synced_tids)
 {
     static_assert(ATOMIC_INT_LOCK_FREE == 2, "std::atomic<int> is not always lock free");
 
@@ -609,9 +609,11 @@ static inline int security_manager_sync_threads_internal(const std::string &app_
 
     LogDebug("number of threads to sync: " << files.size() - 1);
 
+    const pid_t cur_tid = Syscall::gettid();
+    synced_tids.push_back(cur_tid);
+
     if (files.size() > 1) {
         const pid_t cur_pid = getpid();
-        const pid_t cur_tid = Syscall::gettid();
         g_threads_count = files.size() - 1;
 
         struct sigaction act;
@@ -645,9 +647,10 @@ static inline int security_manager_sync_threads_internal(const std::string &app_
         int threads_gone = 0;
         for (auto const &e : files) {
             const int tid = atoi(e.c_str());
+
             if (tid == static_cast<int>(cur_tid))
                 continue;
-
+            synced_tids.push_back(tid); // this will not add current TID (but its already added)
             if (Syscall::tgkill(cur_pid, tid, SIGSETXID) < 0) {
                 const auto err = errno;
                 if (ESRCH == err) // thread already gone
@@ -990,6 +993,7 @@ int security_manager_prepare_app2(const char *app_name, const char *subsession_i
         PrepareAppFlags prepareAppFlags;
         std::vector<gid_t> forbiddenGroups, allowedGroups;
         std::vector<bool> privPathsStatusVector;
+        std::vector<int> synced_tids;
         auto privilegePathMap = MountNS::getPrivilegePathMap(getuid());
         int ret = prepareAppInitialSetupAndFetch(app_name, privilegePathMap, appLabel, pkgName, prepareAppFlags,
                 forbiddenGroups, allowedGroups, privPathsStatusVector);
@@ -1011,14 +1015,38 @@ int security_manager_prepare_app2(const char *app_name, const char *subsession_i
             return ret;
         }
 
-        ret = security_manager_sync_threads_internal(appLabel);
+        ret = security_manager_sync_threads_internal(appLabel, synced_tids);
         if (ret != SECURITY_MANAGER_SUCCESS) {
             LogError("Can't properly setup application threads (Smack label & capabilities) for application " << app_name);
             return ret;
         }
 
         try {
-            CheckProperDrop::checkThreads(prepareAppFlags >> PREPARE_APP_CPD_FLAG_SHIFT);
+            /*
+             * At this point in time, threads detected at security_manager_sync_threads_internal() stage should either:
+             * - be all synchronized (changed Smack label & capabilities)
+             * - be dead (if threads successfully ended)
+             * For those TIDs that are still alive, if their TIDs were on the list during call to sync_threads_internal(),
+             * we can be sure permissions are properly dropped - label_for_self_internal and cap_set_proc() must have finished
+             * with success for those threads, if we reached this point.
+             *
+             * If a thread is dead somehow, but was on the list - it doesn't pose security issue.
+             *
+             * A security issue exists only if a NEW thread was spawned that doesn't exist on the list from
+             * sync_threads_internal(). It can have improper Smack label & caps, so we cannot continue.
+             * Also, we can't reliably check contents of /proc to verify Smack labels, as it can contain
+             * stale information about processes Smack labels even after successful completion of set_label_for_self() function.
+             *
+             * Linux uses sequential TID/PID assignment, so theoretically TIDs can get re-used, but not in such small timeframe,
+             * so we're not taking this issue (TID reusage by kernel) into account here.
+             *
+             * Below call will throw an exception if a new thread will be detected thats not on the original list.
+             *
+             * Previous implementation that actually checked contents of /proc (Smack labels & caps) was dropped
+             * as it was verified experimentally that the proc filesystem could contain stale information about processes
+             * Smack labels/permissions even after successful completion of smack_set_label_for_self() & cap_set_proc() functions.
+             */
+            CheckProperDrop::checkThreads(synced_tids);
         } catch (...) {
             LogError("Privileges haven't been properly dropped for the whole process of application " << app_name);
             /*
index 59b04ca..0d64447 100644 (file)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include <utils.h>
+#include <vector>
 
 namespace SecurityManager {
 namespace CheckProperDrop {
@@ -39,9 +40,6 @@ constexpr inline const char *NS_NAMES[] = {"mnt", "ipc", "net", "uts"};
 constexpr inline auto N_FLAG_BITS = sizeof NS_NAMES / sizeof NS_NAMES[0];
 static_assert(N_FLAG_BITS <= 7, "flags too large for int8_t");
 
-using BitFlags = uint8_t;
-
-
 // SERVICE ONLY
 // Negative on unrecoverable error.
 // Otherwise bit i set iff NS_NAMES[i] checked.
@@ -49,8 +47,7 @@ int8_t computeFlags();
 
 
 // CLIENT ONLY
-// checkProperDropFlags must come from computeFlags()
-void checkThreads(BitFlags flags);
+void checkThreads(const std::vector<int> &synced_tids);
 
 }  // namespace CheckProperDrop
 }  // namespace SecurityManager
index 5f93fb0..c24d9d5 100644 (file)
@@ -93,7 +93,6 @@ SET(SM_TESTS_SOURCES
     ${SM_TEST_SRC}/test_service_impl_utils.cpp
     ${SM_TEST_SRC}/test_smack-labels.cpp
     ${SM_TEST_SRC}/test_smack-rules.cpp
-    ${SM_TEST_SRC}/test_check_proper_drop.cpp
     ${SM_TEST_SRC}/test_message_buffer.cpp
     ${SM_TEST_SRC}/test_misc.cpp
     ${SM_TEST_SRC}/test_template-manager.cpp
diff --git a/test/test_check_proper_drop.cpp b/test/test_check_proper_drop.cpp
deleted file mode 100644 (file)
index 8deef7d..0000000
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (c) 2020 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.
- */
-
-/**
- * @file       test_check_proper_drop.cpp
- * @author     Lukasz Pawelczyk (l.pawelczyk@samsung.com)
- * @version    1.0
- */
-
-#include <chrono>
-#include <condition_variable>
-#include <cstdio>
-#include <functional>
-#include <mutex>
-#include <string>
-#include <thread>
-
-#include <fcntl.h>
-#include <grp.h>
-#include <syscall.h>
-#include <sys/capability.h>
-#include <sys/fsuid.h>
-#include <sys/types.h>
-
-#include <boost/test/unit_test.hpp>
-#include <boost/filesystem.hpp>
-
-#include <check-proper-drop.h>
-#include <dpl/exception.h>
-
-#include <testmacros.h>
-
-using namespace SecurityManager;
-
-namespace {
-
-template <class IsError, class Result>
-Result logErrnoIfError(const IsError &isError, Result res,
-        const char *prettyFunction, unsigned line, const char *expression)
-{
-    if (isError(res))
-        ::fprintf(stderr, "%s(%u) (%s) err (%m)\n", prettyFunction, line, expression);
-    return res;
-}
-
-#define logErrnoIf(...) logErrnoIfError([](auto r) { return r; }, \
-        (__VA_ARGS__), __PRETTY_FUNCTION__, __LINE__, #__VA_ARGS__)
-#define logErrnoIfNot(...) logErrnoIfError([](auto r) { return !r; }, \
-        (__VA_ARGS__), __PRETTY_FUNCTION__, __LINE__, #__VA_ARGS__)
-#define logErrnoIfNegative(...) logErrnoIfError([](auto r) { return r < 0; }, \
-        (__VA_ARGS__), __PRETTY_FUNCTION__, __LINE__, #__VA_ARGS__)
-
-constexpr uid_t APP_UID     = 5001;
-constexpr gid_t APP_GID     = 100;
-
-namespace fs = boost::filesystem;
-namespace ch = std::chrono;
-
-/* In theory after thread has been joined it is guaranteed that the
- * thread has been terminated. In practive its /proc/PID/task entries
- * might still exist and it's sometimes even possible to get its
- * status, caps, label, etc. This fixture class waits for all the
- * threads to be cleaned up properly by the kernel. It's used by tests
- * that require no other threads to exist in the moment the test
- * starts. */
-class NoThreadsAssert
-{
-public:
-    NoThreadsAssert()
-    {
-        const fs::path path = "/proc/self/task";
-        if (!fs::is_directory(path)) {
-            ThrowMsg(CommonException::InternalError,
-                     "Something is wrong with the /proc. Is it mounted?");
-        }
-
-        ch::steady_clock::time_point begin = ch::steady_clock::now();
-
-        for (;;) {
-            size_t n = number_of_entries(path);
-            if (n == 1)
-                break;
-
-            ch::steady_clock::time_point now = ch::steady_clock::now();
-            auto diff = ch::duration_cast<ch::milliseconds>(now - begin).count();
-            if (diff > 3000) {
-                ThrowMsg(CommonException::InternalError,
-                         "Timed out, process still has threads, test cannot proceed");
-            }
-            std::this_thread::sleep_for(ch::milliseconds(10));
-        }
-    }
-
-private:
-    size_t number_of_entries(const fs::path& dir)
-    {
-        size_t count = 0;
-        for (const fs::directory_entry& e: fs::directory_iterator(dir)) {
-            (void)e;
-            ++count;
-        }
-        return count;
-    }
-};
-
-CheckProperDrop::BitFlags getCpdFlags()
-{
-    static const auto cpdFlags = CheckProperDrop::computeFlags();
-    BOOST_REQUIRE(cpdFlags >= 0);
-    return cpdFlags;
-}
-
-bool isProperDrop(CheckProperDrop::BitFlags cpdFlags)
-{
-    try {
-        CheckProperDrop::checkThreads(cpdFlags);
-        return true;
-    } catch (...) {
-        return false;
-    }
-}
-
-bool dropCaps()
-{
-    cap_t cap = logErrnoIfNot(::cap_init());
-    if (!cap)
-        return false;
-    bool capsDropped = logErrnoIf(::cap_set_proc(cap)) == 0;
-    ::cap_free(cap);
-    return capsDropped;
-}
-
-void requireFork(const ::std::function<bool()> &childProcess)
-{
-    pid_t pid = logErrnoIfNegative(::fork());
-    BOOST_REQUIRE(pid >= 0);
-    if (pid > 0) {
-        int res;
-        BOOST_REQUIRE(logErrnoIfNegative(::wait(&res)) == pid);
-        BOOST_REQUIRE(WIFEXITED(res));
-        BOOST_REQUIRE(WEXITSTATUS(res) == EXIT_SUCCESS);
-        return;
-    }
-
-    // Boost test registers some handlers (ex. SIGABRT). If the child process
-    // gets a SIGABRT, the handler causes the test suite to resume execution,
-    // resulting in subsequent tests being executed twice (in the parent and
-    // the child).
-    for (auto signum : { SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
-            SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM,
-            SIGSTKFLT, SIGCHLD, SIGCONT, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG,
-            SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS })
-        ::signal(signum, SIG_DFL);
-
-    // Catch all exceptions to hide them from boost test, thereby preventing
-    // it from interfering. An exception merely means that the test failed.
-    bool childProcessOk;
-    try {
-        childProcessOk = childProcess();
-    } catch (...) {
-        childProcessOk = false;
-    }
-
-    // bypass boost test atexit handlers
-    ::_exit(childProcessOk ? EXIT_SUCCESS : EXIT_FAILURE);
-}
-
-void requireThread(const ::std::function<bool()> &alterThreadCreds,
-        bool isProper = false, CheckProperDrop::BitFlags cpdFlags = getCpdFlags())
-{
-    requireFork([&] {
-        ::std::mutex mutex;
-        ::std::condition_variable cond;
-        bool kidOk = false;
-        bool nowKid = true;
-
-        auto enter = [&](::std::unique_lock<::std::mutex> &lock, bool isKid) {
-            lock.lock();
-            cond.wait(lock, [&]{ return isKid == nowKid; });
-        };
-        auto leave = [&](::std::unique_lock<::std::mutex> &lock, bool isKid) {
-            nowKid = !isKid;
-            lock.unlock();
-            cond.notify_one();
-        };
-
-        ::std::thread kid([&] {
-            kidOk = alterThreadCreds();
-
-            ::std::unique_lock<::std::mutex> lock(mutex);
-            leave(lock, true);  // creds altered, signal parent
-            enter(lock, true);  // parent done checking, terminate
-        });
-
-        ::std::unique_lock<::std::mutex> lock(mutex, ::std::defer_lock);
-        enter(lock, false);  // kid has altered creds
-        bool ok = dropCaps() && isProperDrop(cpdFlags) == isProper;
-
-        leave(lock, false);  // check done, signal kid
-        kid.join();
-
-        return ok && kidOk;
-    });
-}
-
-void requireThreadNs(const char *ns, int unshareFlag)
-{
-    size_t nsIdx = 0;
-    for (; nsIdx < CheckProperDrop::N_FLAG_BITS; nsIdx++)
-        if (!strcmp(ns, CheckProperDrop::NS_NAMES[nsIdx]))
-            break;
-    BOOST_REQUIRE(nsIdx < CheckProperDrop::N_FLAG_BITS);
-    auto cpdFlags = getCpdFlags();
-    auto nsBit = 1 << nsIdx;
-    auto unshareThenDropCaps = [&] {
-        return logErrnoIf(::unshare(unshareFlag)) == 0 && dropCaps();
-    };
-
-    if (cpdFlags & nsBit)
-        requireThread(unshareThenDropCaps);
-    else
-        ::fprintf(stderr, "%s namespace not included in checkProperDrop flags, "
-                          "omitting failure test\n", ns);
-
-    requireThread(unshareThenDropCaps, true, cpdFlags & ~nsBit);
-}
-
-}  // namespace
-
-
-BOOST_AUTO_TEST_SUITE(CHECK_PROPER_DROP_TEST)
-
-POSITIVE_FIXTURE_TEST_CASE(T1700__no_threads, NoThreadsAssert)
-{
-    auto cpdFlags = getCpdFlags();
-    requireFork([&] {
-        return dropCaps() && isProperDrop(cpdFlags);
-    });
-}
-
-// This may happen when a rogue thread:
-// * Survives sync_threads_internal due to a lucky interleaving.
-// * Uses capset() to restore main thread capabilities to nonzero.
-// * Terminates before checkProperDrop() starts.
-NEGATIVE_FIXTURE_TEST_CASE(T1701__no_threads_privileged, NoThreadsAssert)
-{
-    auto cpdFlags = getCpdFlags();
-    requireFork([&] {
-        return !isProperDrop(cpdFlags);
-    });
-}
-
-POSITIVE_FIXTURE_TEST_CASE(T1702__thread_unmodified, NoThreadsAssert)
-{
-    requireThread(dropCaps, true);
-}
-
-// Caps not dropped.
-NEGATIVE_FIXTURE_TEST_CASE(T1703__thread_privileged, NoThreadsAssert)
-{
-    requireThread([]{ return true; });
-}
-
-// Wrong label.
-NEGATIVE_FIXTURE_TEST_CASE(T1704__thread_mislabeled, NoThreadsAssert)
-{
-    requireThread([]{
-        int fd = logErrnoIfNegative(TEMP_FAILURE_RETRY(::open(
-                ("/proc/" + ::std::to_string(syscall(SYS_gettid)) + "/attr/current").c_str(),
-                O_WRONLY)));
-        if (fd < 0)
-            return false;
-        static constexpr char label[] = "rogueLabel";
-        static constexpr auto labelLen = ::strlen(label);
-        auto written = logErrnoIfNegative(TEMP_FAILURE_RETRY(::write(fd, label, labelLen)));
-        ::close(fd);
-        if (written != labelLen) {
-            if (written >= 0)
-                ::fprintf(stderr, "short smack label write, written=%u\n", unsigned(written));
-            return false;
-        }
-        return dropCaps();
-    });
-}
-
-// Wrong mnt ns, failure iff ns included in CheckProperDrop::computeFlags().
-NEGATIVE_FIXTURE_TEST_CASE(T1705__thread_ns_mnt, NoThreadsAssert)
-{
-    requireThreadNs("mnt", CLONE_NEWNS);
-}
-
-NEGATIVE_FIXTURE_TEST_CASE(T1706__thread_ns_ipc, NoThreadsAssert)
-{
-    requireThreadNs("ipc", CLONE_NEWIPC);
-}
-
-// Wrong net ns, failure iff ns included in CheckProperDrop::computeFlags().
-NEGATIVE_FIXTURE_TEST_CASE(T1707__thread_ns_net, NoThreadsAssert)
-{
-    requireThreadNs("net", CLONE_NEWNET);
-}
-
-// Wrong uts ns, failure iff ns included in CheckProperDrop::computeFlags().
-NEGATIVE_FIXTURE_TEST_CASE(T1708__thread_ns_uts, NoThreadsAssert)
-{
-    requireThreadNs("uts", CLONE_NEWUTS);
-}
-
-// Wrong uid.
-NEGATIVE_FIXTURE_TEST_CASE(T1709__thread_uid, NoThreadsAssert)
-{
-    requireThread([]{
-        ::setfsuid(APP_UID);
-        return dropCaps();
-    });
-}
-
-// Wrong gid.
-NEGATIVE_FIXTURE_TEST_CASE(T1710__thread_gid, NoThreadsAssert)
-{
-    requireThread([]{
-        ::setfsgid(APP_GID);
-        return dropCaps();
-    });
-}
-
-// Wrong supplementary groups.
-NEGATIVE_FIXTURE_TEST_CASE(T1711__thread_groups, NoThreadsAssert)
-{
-    requireThread([]{
-        ::gid_t dummy;
-        // Use setgroups syscall directly to affect this thread only
-        // (see man getgroups(2), NOTES, C library/kernel differences)
-        return logErrnoIf(static_cast<int>(::syscall(SYS_setgroups, 0, &dummy))) == 0
-                && dropCaps();
-    });
-}
-
-BOOST_AUTO_TEST_SUITE_END()