Replace magic policy level strings with constexpr
[platform/core/test/security-tests.git] / src / security-manager-tests / test_cases_prepare_app.cpp
index bebb90a..32dad32 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
+ * Copyright (c) 2016-2020 Samsung Electronics Co., Ltd. All rights reserved
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
  *    limitations under the License.
  */
 
+#include <poll.h>
 #include <sys/smack.h>
 #include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/eventfd.h>
 
+#include <cmath>
 #include <thread>
 #include <string>
 #include <memory>
 #include <mutex>
+#include <fstream>
 
 #include <dpl/test/test_runner_child.h>
 #include <dpl/test/test_runner.h>
@@ -38,7 +43,15 @@ namespace {
 bool finish = false;
 const size_t THREADS = 10;
 
-const std::string SYNC_TEST_APP("app100");
+const std::string APP_TEST_USER = "app_test_user";
+
+const std::string EXTERNAL_STORAGE_PRIVILEGE = "http://tizen.org/privilege/externalstorage";
+const std::string MEDIA_STORAGE_PRIVILEGE = "http://tizen.org/privilege/mediastorage";
+
+const std::string ACCESS_DENIED_DIR_PATH = "/usr/share/security-manager/dummy";
+const std::string EXTERNAL_STORAGE_DIR_PATH = "/opt/media";
+const std::string MEDIA_STORAGE_RW_DIR_PATH = "/opt/usr/media";
+const std::string MEDIA_STORAGE_RO_DIR_PATH = "/opt/usr/home/app_test_user/media";
 
 typedef std::unique_ptr<_cap_struct, decltype(&cap_free)> CapPtr;
 
@@ -108,13 +121,44 @@ struct ThreadWrapper
     std::thread thread;
 };
 
+ino_t getFileInode(const std::string &path)
+{
+    struct stat st;
+    if (stat(path.c_str(), &st) != 0)
+        return 0;
+
+    return st.st_ino;
+}
+
+std::string getTextFileContents(const std::string &path)
+{
+    std::ifstream in(path.c_str());
+    if (in.fail())
+        return std::string();
+    std::stringstream ss;
+    ss << in.rdbuf();
+    return ss.str();
+}
+
+bool isPathBound(const std::string &what, const std::string &where, pid_t pid = 1)
+{
+    std::string mountinfoPath = std::string("/proc/") + std::to_string(pid) + "/mountinfo";
+    std::string mountinfo = getTextFileContents(mountinfoPath);
+    std::string line = what + " " + where;
+
+    return std::string::npos != mountinfo.find(line);
+}
+
 } // anonymous namespace
 
 RUNNER_TEST_GROUP_INIT(SECURITY_MANAGER_PREPARE_APP)
 
 RUNNER_CHILD_TEST(security_manager_100_synchronize_credentials_test)
 {
-    AppInstallHelper app(SYNC_TEST_APP.c_str());
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    AppInstallHelper app("app100", tmpUser.getUid());
     ScopedInstaller appInstall(app);
     const std::string expectedLabel = app.generateAppLabel();
 
@@ -122,6 +166,8 @@ RUNNER_CHILD_TEST(security_manager_100_synchronize_credentials_test)
     RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
     if (pid == 0) {
         {
+            RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+            Api::prepareAppCandidate();
             ThreadWrapper threads[THREADS];
 
             for (size_t i = 0; i < THREADS; i++)
@@ -133,5 +179,387 @@ RUNNER_CHILD_TEST(security_manager_100_synchronize_credentials_test)
         exit(0);
     } else {
         waitPid(pid);
+        Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
+    }
+}
+
+RUNNER_CHILD_TEST(security_manager_101_create_namespace_test_n)
+{
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    AppInstallHelper app("app100_n", tmpUser.getUid());
+    ScopedInstaller appInstall(app);
+    const std::string expectedLabel = app.generateAppLabel();
+
+    pid_t pid = fork();
+    RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
+    if (pid == 0) {
+        {
+            RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+            ThreadWrapper threads[THREADS];
+
+            for (size_t i = 0; i < THREADS; i++)
+                threads[i].run(i, expectedLabel);
+
+            Api::prepareAppCandidate(SECURITY_MANAGER_ERROR_INPUT_PARAM);
+        }
+        RUNNER_ASSERT_MSG(!thread_errors.empty(), std::endl << thread_errors);
+        exit(0);
+    } else {
+        waitPid(pid);
+    }
+}
+
+RUNNER_CHILD_TEST(security_manager_101_create_namespace_test)
+{
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    AppInstallHelper app("app101", tmpUser.getUid());
+    ScopedInstaller appInstall(app);
+
+    SynchronizationPipe synchPipe;
+    pid_t pid = fork();
+    RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
+    if (pid == 0) {
+        synchPipe.claimParentEp();
+        RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+        Api::prepareAppCandidate();
+        Api::prepareApp(app.getAppId().c_str());
+        synchPipe.post();
+        synchPipe.wait();
+
+        exit(0);
+    } else {
+        synchPipe.claimChildEp();
+        synchPipe.wait();
+
+        std::string appBindPath = std::string("/var/run/user/") + std::to_string(tmpUser.getUid())
+                                  + "/apps/" + app.generateAppLabel() + "/" + std::to_string(pid);
+        std::string appProcPath = std::string("/proc/") + std::to_string(pid) + "/ns/mnt";
+        std::string launcherProcPath = std::string("/proc/") + std::to_string(getpid()) + "/ns/mnt";
+
+        ino_t appBindInode = getFileInode(appBindPath);
+        ino_t appProcInode = getFileInode(appProcPath);
+        ino_t launcherProcInode = getFileInode(launcherProcPath);
+
+        RUNNER_ASSERT_ERRNO_MSG(appBindInode != 0, "get inode failed");
+        RUNNER_ASSERT_ERRNO_MSG(appProcInode != 0, "get inode failed");
+        RUNNER_ASSERT_ERRNO_MSG(launcherProcInode != 0, "get inode failed");
+
+        RUNNER_ASSERT_ERRNO_MSG(launcherProcInode != appProcInode, "create mount namespace failed");
+        RUNNER_ASSERT_ERRNO_MSG(appBindInode == appProcInode, "bind namespace failed");
+
+        synchPipe.post();
+        waitPid(pid);
+        Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
+    }
+}
+
+RUNNER_CHILD_TEST(security_manager_102_check_propagation_test)
+{
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    AppInstallHelper app("app102", tmpUser.getUid());
+    ScopedInstaller appInstall(app);
+
+    SynchronizationPipe synchPipe;
+    pid_t pid = fork();
+    RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
+    if (pid == 0) {
+        synchPipe.claimParentEp();
+        RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+        Api::prepareAppCandidate();
+        Api::prepareApp(app.getAppId().c_str());
+        synchPipe.post();
+        synchPipe.wait();
+
+        exit(0);
+    } else {
+        synchPipe.claimChildEp();
+        synchPipe.wait();
+
+        bool result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+
+        synchPipe.post();
+        waitPid(pid);
+        Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
+    }
+}
+
+RUNNER_CHILD_TEST(security_manager_103_policy_change_test)
+{
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    AppInstallHelper app("app103", tmpUser.getUid());
+    app.addPrivilege(EXTERNAL_STORAGE_PRIVILEGE);
+    app.addPrivilege(MEDIA_STORAGE_PRIVILEGE);
+    ScopedInstaller appInstall(app);
+
+    SynchronizationPipe synchPipe;
+    pid_t pid = fork();
+    RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
+    if (pid == 0) {
+        synchPipe.claimParentEp();
+        RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+        Api::prepareAppCandidate();
+        Api::prepareApp(app.getAppId().c_str());
+        synchPipe.post();
+        synchPipe.wait();
+
+        exit(0);
+    } else {
+        synchPipe.claimChildEp();
+        synchPipe.wait();
+
+        bool result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+
+        PolicyRequest policyRequest;
+        PolicyEntry policyEntry(app.getAppId(), std::to_string(tmpUser.getUid()), EXTERNAL_STORAGE_PRIVILEGE);
+        policyEntry.setLevel(PolicyEntry::LEVEL_DENY);
+        policyRequest.addEntry(policyEntry);
+
+        policyEntry = PolicyEntry(app.getAppId(), std::to_string(tmpUser.getUid()), MEDIA_STORAGE_PRIVILEGE);
+        policyEntry.setLevel(PolicyEntry::LEVEL_DENY);
+        policyRequest.addEntry(policyEntry);
+        Api::sendPolicy(policyRequest);
+
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == true, "path is not bound");
+
+        policyEntry = PolicyEntry(app.getAppId(),  std::to_string(tmpUser.getUid()), EXTERNAL_STORAGE_PRIVILEGE);
+        policyEntry.setLevel(PolicyEntry::LEVEL_ALLOW);
+        policyRequest.addEntry(policyEntry);
+
+        policyEntry = PolicyEntry(app.getAppId(),  std::to_string(tmpUser.getUid()), MEDIA_STORAGE_PRIVILEGE);
+        policyEntry.setLevel(PolicyEntry::LEVEL_ALLOW);
+        policyRequest.addEntry(policyEntry);
+        Api::sendPolicy(policyRequest);
+
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, EXTERNAL_STORAGE_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RW_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+        result = isPathBound(ACCESS_DENIED_DIR_PATH, MEDIA_STORAGE_RO_DIR_PATH, pid);
+        RUNNER_ASSERT_ERRNO_MSG(result == false, "path is bound");
+
+        synchPipe.post();
+        waitPid(pid);
+        Api::cleanupApp(app.getAppId().c_str(), tmpUser.getUid(), pid);
+    }
+}
+
+namespace {
+class Timestamp {
+    uint64_t _;
+    explicit Timestamp(uint64_t ts) : _(ts) {}
+public:
+    Timestamp operator-(const Timestamp &other) const {
+        RUNNER_ASSERT(_ > other._);
+        return Timestamp(_ - other._);
+    }
+    Timestamp operator+(const Timestamp &other) const {
+        return Timestamp(_ + other._);
+    }
+    bool operator<(const Timestamp &other) const {
+        return _ < other._;
+    }
+    Timestamp() = default;
+    static Timestamp future(uint64_t ns) {
+        timespec ts;
+        const auto res = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+        RUNNER_ASSERT_ERRNO(!res);
+        return Timestamp(ts.tv_sec * 1000000000ULL + ts.tv_nsec + ns);
+    }
+    static Timestamp now() {
+        return future(0);
+    }
+    template <size_t nLowDigitsToSkip = 3>
+    static void report(Timestamp *ts, size_t n) {
+        std::sort(ts, ts+n);
+        uint64_t sum = 0, mn = -1, mx = 0;
+        long double qsum = 0;
+        for (size_t i = 0; i < n; i++) {
+            const auto t = ts[i]._;
+            sum += t;
+            qsum += decltype(qsum)(t) * t;
+            mn = std::min(mn, t);
+            mx = std::max(mx, t);
+        }
+        uint64_t avg = sum / n;
+        auto qstddev = qsum/n - decltype(qsum)(avg)*avg;
+        const auto out = [](const char *desc, uint64_t t) {
+            char raw[20];
+        char s[20 + 20/3 + 1];
+        size_t j = 0, i = 0;
+        do
+            raw[j++] = '0' + t % 10ULL;
+        while (t /= 10ULL);
+        for (;;) {
+            s[i++] = raw[--j];
+            if (j <= nLowDigitsToSkip)
+                break;
+            if (!(j % 3))
+                s[i++] = ' ';
+        }
+        s[i] = '\0';
+        std::cerr << desc << s;
+    };
+    out("min ", mn);
+    out(" max ", mx);
+    out(" avg ", avg);
+    out(" median ", ts[n/2]._);
+    out(" stddev ", std::floor(std::sqrt(qstddev)));
+    std::cerr << '\n';
+    }
+};
+
+template <class T, size_t N>
+constexpr size_t arraySize(T (&)[N]) { return N; }
+} // namespace
+
+RUNNER_TEST(security_manager_200_prepare_app_perf)
+{
+    constexpr int8_t nThreads = 32;
+    constexpr int8_t nConcurrentAppsSamples[] = { 0 /* 1 app w/ nThreads */, 1, 2, 4, 8, 16, 32 };
+    constexpr uint64_t minTotalBenchTime = 60 * 1000ULL*1000*1000; // 60s
+
+    TemporaryTestUser tmpUser(APP_TEST_USER, GUM_USERTYPE_NORMAL, false);
+    tmpUser.create();
+
+    struct App {
+        AppInstallHelper hlp;
+        pid_t pid;
+    };
+
+    std::vector<Timestamp> candidate, prepare, everything;
+
+    std::vector<App> apps;
+    std::vector<ScopedInstaller> appInstalls;
+
+    constexpr auto nAppsMax = nConcurrentAppsSamples[arraySize(nConcurrentAppsSamples) - 1] ?: 1;
+    apps.reserve(nAppsMax);
+    appInstalls.reserve(nAppsMax);
+
+    const auto uid = tmpUser.getUid();
+    for (int i = 0; i < nAppsMax; i++) {
+        apps.emplace_back(App{AppInstallHelper("app200_" + std::to_string(i), uid), 0});
+        auto &hlp = apps.back().hlp;
+        for (const auto &p : { EXTERNAL_STORAGE_PRIVILEGE, MEDIA_STORAGE_PRIVILEGE,
+                std::string("http://tizen.org/privilege/camera"),
+                std::string("http://tizen.org/privilege/internet") })
+            hlp.addPrivilege(p);
+        hlp.createSharedRODir();
+        appInstalls.emplace_back(ScopedInstaller(hlp));
+    }
+
+    for (const auto nConcurrentAppsDesc : nConcurrentAppsSamples) {
+        const auto nConcurrentApps = nConcurrentAppsDesc ?: 1;
+        const auto timeout = Timestamp::future(minTotalBenchTime / arraySize(nConcurrentAppsSamples));
+        do {
+            SynchronizationPipe synchPipe;
+            auto exitEvFd = eventfd(0, 0);
+            RUNNER_ASSERT(exitEvFd >= 0);
+
+            for (int i = 0; i < nConcurrentApps; i++) {
+                auto &app = apps[i];
+                const auto pid = fork();
+                RUNNER_ASSERT_ERRNO_MSG(pid >= 0, "Fork failed");
+                if (pid)
+                    app.pid = pid;
+                else {
+                    synchPipe.claimChildEp();
+                    RUNNER_ASSERT_ERRNO_MSG(setLauncherSecurityAttributes(tmpUser) == 0, "launcher failed");
+
+                    const auto appId = app.hlp.getAppId();
+
+                    synchPipe.post(); // declare readiness to start measuring
+                    synchPipe.pollForWait(); // wait for parent to signal all kids simultaneously
+
+                    const auto candBeg = Timestamp::now();
+                    Api::prepareAppCandidate();
+                    const auto candEnd = Timestamp::now();
+
+                    if (!nConcurrentAppsDesc) {
+                        for (int i = 0; i < nThreads; i++)
+                            std::thread([]{ for (;;) usleep(1000); }).detach();
+                    }
+
+                    const auto prepBeg = Timestamp::now();
+                    Api::prepareApp(appId);
+                    const auto prepEnd = Timestamp::now();
+
+                    const Timestamp ts[2] = { candEnd-candBeg, prepEnd-prepBeg };
+                    synchPipe.post(ts, sizeof ts); // post measurement payload
+
+                    // stay idle until all kids are done to simulate idle apps and reduce benchmark noise
+                    pollfd fds[1];
+                    fds->fd = exitEvFd;
+                    fds->events = POLLIN;
+                    auto ret = TEMP_FAILURE_RETRY(poll(fds, 1, -1));
+                    RUNNER_ASSERT_ERRNO(ret > 0);
+
+                    exit(0);
+                }
+            }
+            synchPipe.claimParentEp();
+
+            for (int i = 0; i < nConcurrentApps; i++) // wait for all kids to be ready to start measurement
+                synchPipe.wait();
+            synchPipe.post(); // signal all kids to start measuring
+
+            for (int i = 0; i < nConcurrentApps; i++) {
+                Timestamp ts[2];
+                synchPipe.wait(ts, sizeof ts);
+                candidate.emplace_back(ts[0]);
+                prepare.emplace_back(ts[1]);
+                everything.emplace_back(ts[0] + ts[1]);
+            }
+
+            RUNNER_ASSERT(!eventfd_write(exitEvFd, 1)); // signal all kids to exit now
+
+            for (int i = 0; i < nConcurrentApps; i++) {
+                const auto &app = apps[i];
+                waitPid(app.pid);
+                Api::cleanupApp(app.hlp.getAppId(), uid, app.pid);
+            }
+        } while (Timestamp::now() < timeout);
+
+        if (!nConcurrentAppsDesc)
+            std::cerr << "additionalThreads " << int(nThreads) << ' ';
+        std::cerr << "nConcurrentApps " << int(nConcurrentApps) << " samples " << candidate.size() << '\n';
+        std::cerr << "  prepareAppCandidate [us]:              ";
+        Timestamp::report(candidate.data(), candidate.size());
+        std::cerr << "  prepareApp [us]:                       ";
+        Timestamp::report(prepare.data(), prepare.size());
+        std::cerr << "  prepareAppCandidate + prepareApp [us]: ";
+        Timestamp::report(everything.data(), everything.size());
+        candidate.clear();
+        prepare.clear();
+        everything.clear();
     }
 }