/*
- * 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>
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;
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();
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++)
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();
}
}