From 63fc0d6226416f407b2e9069c8568108b05d188e Mon Sep 17 00:00:00 2001 From: Dariusz Michaluk Date: Mon, 2 Oct 2017 15:14:48 +0200 Subject: [PATCH] Modify app launched in mount namespace This commit adds worker that will be able to manage with mount namespace. If mount namespace is not supported, security-manager will run without worker, otherwise worker will be communicated with security-manager through IPC channel. If app privilege status changes, worker will allow/deny access to filesystem directory associated with this privilege. Change-Id: I056cd752c228335c7b67a607bddc0934c7a79ddd --- src/common/CMakeLists.txt | 2 + src/common/include/filesystem-exception.h | 1 + src/common/include/nsmount-logic.h | 83 +++++++++++++++++++ src/common/include/service_impl.h | 10 +++ src/common/include/worker.h | 46 +++++++++++ src/common/nsmount-logic.cpp | 130 ++++++++++++++++++++++++++++++ src/common/service_impl.cpp | 7 ++ src/common/worker.cpp | 117 +++++++++++++++++++++++++++ src/server/main/server-main.cpp | 81 +++++++++++++++---- src/server/main/socket-manager.cpp | 6 ++ src/server/service/include/service.h | 5 +- 11 files changed, 472 insertions(+), 16 deletions(-) create mode 100644 src/common/include/nsmount-logic.h create mode 100644 src/common/include/worker.h create mode 100644 src/common/nsmount-logic.cpp create mode 100644 src/common/worker.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 089d53e..1821c5a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -57,12 +57,14 @@ SET(COMMON_SOURCES ${COMMON_PATH}/permissible-set.cpp ${COMMON_PATH}/protocols.cpp ${COMMON_PATH}/message-buffer.cpp + ${COMMON_PATH}/nsmount-logic.cpp ${COMMON_PATH}/privilege_db.cpp ${COMMON_PATH}/smack-labels.cpp ${COMMON_PATH}/smack-rules.cpp ${COMMON_PATH}/smack-check.cpp ${COMMON_PATH}/service_impl.cpp ${COMMON_PATH}/tzplatform-config.cpp + ${COMMON_PATH}/worker.cpp ${COMMON_PATH}/privilege-info.cpp ${COMMON_PATH}/privilege-gids.cpp ${COMMON_PATH}/group2gid.cpp diff --git a/src/common/include/filesystem-exception.h b/src/common/include/filesystem-exception.h index fd4bd73..292d76b 100644 --- a/src/common/include/filesystem-exception.h +++ b/src/common/include/filesystem-exception.h @@ -33,6 +33,7 @@ class Exception { public: DECLARE_EXCEPTION_TYPE(SecurityManager::Exception, Base) DECLARE_EXCEPTION_TYPE(Base, FileError) + DECLARE_EXCEPTION_TYPE(Base, InvalidArgument) }; } // namespace FS diff --git a/src/common/include/nsmount-logic.h b/src/common/include/nsmount-logic.h new file mode 100644 index 0000000..5f25c3b --- /dev/null +++ b/src/common/include/nsmount-logic.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Rafal Krypa + * + * 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 + * + * Security Manager NSS library + */ +/* + * @file nsmount-logic.h + * @author Bartlomiej Grzelewski + * @version 1.0 + * @brief + */ + + +#pragma once + +#include +#include + +namespace SecurityManager { + +class NSMountLogic { +public: + NSMountLogic(Cynara &cynara, Channel &channel) + : m_cynara(cynara) + , m_channel(channel) + {} + + struct Entry : public ISerializable { + typedef std::string Privilege; + typedef std::pair PrivilegeStatus; + typedef std::vector> PrivilegeStatusVector; + + Entry() {} + + Entry(uid_t uid, std::string appName) + : uid(uid) + , appName(std::move(appName)) + {} + + explicit Entry(IStream &stream) { + Deserialization::Deserialize(stream, uid, appName, smackLabel, privilegeStatusVector); + } + + virtual void Serialize(IStream &stream) const { + Serialization::Serialize(stream, uid, appName, smackLabel, privilegeStatusVector); + } + + uid_t uid = 0; + std::string appName; + std::string smackLabel; + PrivilegeStatusVector privilegeStatusVector; + }; + + typedef std::vector EntryVector; + + bool check(std::function cynaraFilter); + + virtual ~NSMountLogic(); +protected: + void readFiles(void); + void cynaraCheck(void); + bool sendJobs(); + + EntryVector m_entryVector; + Cynara &m_cynara; + Channel &m_channel; +}; + +} // namespace SecurityManager diff --git a/src/common/include/service_impl.h b/src/common/include/service_impl.h index 2424a3d..22a65e7 100644 --- a/src/common/include/service_impl.h +++ b/src/common/include/service_impl.h @@ -28,6 +28,7 @@ #include +#include "channel.h" #include "credentials.h" #include "cynara.h" #include "security-manager.h" @@ -300,6 +301,14 @@ public: */ int appBindNamespace(const Credentials &creds, const std::string &appName); + /** + * Register channel for communication with worker process. + * + * @param[in] channel + * + */ + void RegisterChannel(Channel channel) { m_channel = std::move(channel); } + private: bool authenticate(const Credentials &creds, const std::string &privilege); @@ -364,6 +373,7 @@ private: PrivilegeDb m_privilegeDb; CynaraAdmin m_cynaraAdmin; PrivilegeGids m_privilegeGids; + Channel m_channel; }; } /* namespace SecurityManager */ diff --git a/src/common/include/worker.h b/src/common/include/worker.h new file mode 100644 index 0000000..79abdb0 --- /dev/null +++ b/src/common/include/worker.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Rafal Krypa + * + * 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 worker.h + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief + */ +#pragma once + +#include + +#include +#include + +namespace SecurityManager { + +class Worker { +public: + typedef NSMountLogic::Entry Entry; + + Worker(Channel channel); + int doWork(const NSMountLogic::EntryVector &entryVector); + + void mainLoop(); + +protected: + Channel m_channel; +}; + +} // namespace SecurityManager diff --git a/src/common/nsmount-logic.cpp b/src/common/nsmount-logic.cpp new file mode 100644 index 0000000..aef596b --- /dev/null +++ b/src/common/nsmount-logic.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Rafal Krypa + * + * 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 nsmount-logic.cpp + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief Logic for modifying up existing mount namespace. + */ +#include + +#include +#include +#include +#include +#include + +#include + +namespace { + +auto notNumber = [](const std::string &input) +{ + try { + stoi(input); + } catch (const std::invalid_argument&) { + return true; + } + return false; +}; + +auto toNumber = [](const std::string &input) +{ + uid_t uid; + try { + uid = static_cast(stoi(input)); + } catch (const std::invalid_argument &) { + ThrowMsg(FS::Exception::InvalidArgument, "Cound not translate " << input << " to uid_t"); + } catch (const std::out_of_range &e) { + ThrowMsg(FS::Exception::InvalidArgument, "Cound not translate " << input << " to uid_t"); + } + return uid; +}; + +} // namespace anonymous + +namespace SecurityManager { + +void NSMountLogic::readFiles(void) +{ + auto users = FS::getSubDirectoriesFromDirectory(MountNS::getUsersAppsMountPointsPath(), true); + auto last = std::remove_if(users.begin(), users.end(), notNumber); + users.erase(last, users.end()); + + for (auto &user : users) { + auto apps = FS::getFilesFromDirectory(MountNS::getUserAppsMountPointsPath(toNumber(user))); + for (auto &app : apps) { + m_entryVector.emplace_back(toNumber(user), app); + } + } +} + +void NSMountLogic::cynaraCheck(void) +{ + for (auto &entry : m_entryVector) { + if (entry.smackLabel.empty()) + continue; + + auto storagePrivilegePathMap = MountNS::getPrivilegePathMap(entry.uid); + + for (auto &p : storagePrivilegePathMap) { + entry.privilegeStatusVector.emplace_back( + std::make_pair(p.first, m_cynara.check(entry.smackLabel, p.first, std::to_string(entry.uid), std::string()))); + } + } +} + +bool NSMountLogic::sendJobs(void) +{ + int status; + MessageBuffer send, recv; + Serialization::Serialize(send, m_entryVector); + if (!m_channel.write(send)) { + LogError("Could not send data to worker!"); + return false; + } + if (!m_channel.read(recv)) { + LogError("Could not recv data from worker!"); + return false; + } + Deserialization::Deserialize(recv, status); + return status == 0; +} + +bool NSMountLogic::check(std::function cynaraFilter) +{ + try { + readFiles(); + + for (auto &entry : m_entryVector) + cynaraFilter(entry); + + cynaraCheck(); + return sendJobs(); + } catch (const MessageBuffer::Exception::Base &e) { + LogError("Worker connection failed: " << e.DumpToString()); + } catch (const FS::Exception::Base &e) { + LogError("Filesystem exception: " << e.DumpToString()); + } + return false; +} + +NSMountLogic::~NSMountLogic() +{} + +} // namespace SecurityManager diff --git a/src/common/service_impl.cpp b/src/common/service_impl.cpp index 2c33722..1fb8267 100644 --- a/src/common/service_impl.cpp +++ b/src/common/service_impl.cpp @@ -43,6 +43,7 @@ #include #include +#include #include "protocols.h" #include "privilege_db.h" #include "cynara.h" @@ -1069,6 +1070,12 @@ int ServiceImpl::policyUpdate(const Credentials &creds, const std::vector + * + * 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 worker.cpp + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief + */ + +#include + +#include +#include +#include +#include +#include +#include + +namespace SecurityManager { + +Worker::Worker(Channel channel) + : m_channel(std::move(channel)) +{} + +int Worker::doWork(const NSMountLogic::EntryVector &entries) +{ + int status = 0; + bool inGlobalNamespace = false; + + for (auto &entry : entries) { + try { + // in most cases entry.uid will not change between iterations + auto storagePrivilegePathMap = MountNS::getPrivilegePathMap(entry.uid); + + if (!inGlobalNamespace && MountNS::enterMountNamespace(MountNS::MAIN_MOUNT_NAMESPACE)) { + inGlobalNamespace = true; + } + + if (!inGlobalNamespace) { + LogError("Error entering global mount namespace. Environment of application: " + << entry.appName << " will not be setup correctly."); + continue; + } + + if (MountNS::enterMountNamespace(MountNS::getUserAppMountPointPath(entry.uid, entry.appName))) { + inGlobalNamespace = false; + } else { + continue; + } + + for (auto &privStatus : entry.privilegeStatusVector) { + auto &privName = privStatus.first; + auto &result = privStatus.second; + auto mapIter = storagePrivilegePathMap.find(privName); + + if (mapIter == storagePrivilegePathMap.end()) + continue; + + MountNS::PathVector pathVector = mapIter->second; + for (auto &path : pathVector) { + bool allowed = !MountNS::isPathBound(MountNS::ACCESS_DENIED_DIR_PATH, path); + + if (allowed == result) + continue; + + if (result) + MountNS::uMount(path); // allow access to path + else + MountNS::bindMount(MountNS::ACCESS_DENIED_DIR_PATH, path); // forbid access + } + } + } catch (...) { + status = -1; + LogError("Could not set up access to path for application: " << entry.appName); + } + } + return status; +} + +void Worker::mainLoop() +{ + NSMountLogic::EntryVector entryVector; + + do { + int status; + NSMountLogic::EntryVector entryVector; + MessageBuffer recv; + MessageBuffer send; + + if (!m_channel.read(recv)) { + LogError("Error reading command socket. The Security-manager worker will exit"); + break; + } + + Deserialization::Deserialize(recv, entryVector); + status = doWork(entryVector); + Serialization::Serialize(send, status); + m_channel.write(send); + } while (true); +} + +} // namespace SecurityManager diff --git a/src/server/main/server-main.cpp b/src/server/main/server-main.cpp index 2a9a00f..7b71f7d 100644 --- a/src/server/main/server-main.cpp +++ b/src/server/main/server-main.cpp @@ -29,24 +29,29 @@ #include +#include +#include #include #include #include +#include -#define REGISTER_SOCKET_SERVICE(manager, service) \ - registerSocketService(manager, #service) +#define REGISTER_SOCKET_SERVICE(manager, service, channel) \ + registerSocketService(manager, #service, channel) template -bool registerSocketService(SecurityManager::SocketManager &manager, - const std::string& serviceName) +T* registerSocketService(SecurityManager::SocketManager &manager, + const std::string& serviceName, + Channel channel) { T *service = NULL; try { service = new T(); + service->RegisterChannel(std::move(channel)); service->Start(); manager.RegisterSocketService(service); - return true; + return service; } catch (const SecurityManager::Exception &exception) { LogError("Error in creating service " << serviceName << ", details:\n" << exception.DumpToString()); @@ -61,37 +66,83 @@ bool registerSocketService(SecurityManager::SocketManager &manager, service->Stop(); delete service; } - return false; + return nullptr; +} + +int worker(Channel channel) +{ + SecurityManager::Singleton::Instance().SetTag("SECURITY_MANAGER_WORKER"); + SecurityManager::Worker worker(std::move(channel)); + worker.mainLoop(); + return 0; +} + +int security_manager(Channel channel) +{ + SecurityManager::FileLocker serviceLock(SecurityManager::SERVICE_LOCK_FILE, true); + LogInfo("Start!"); + SecurityManager::SocketManager manager; + + if (!REGISTER_SOCKET_SERVICE(manager, SecurityManager::Service, std::move(channel))) { + LogError("Unable to create socket service. Exiting."); + return EXIT_FAILURE; + } + + manager.MainLoop(); + return 0; } int main() { UNHANDLED_EXCEPTION_HANDLER_BEGIN { - // initialize logging SecurityManager::Singleton::Instance().SetTag("SECURITY_MANAGER"); - SecurityManager::FileLocker serviceLock(SecurityManager::SERVICE_LOCK_FILE, true); - sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGPIPE); + sigaddset(&mask, SIGCHLD); + if (-1 == pthread_sigmask(SIG_BLOCK, &mask, NULL)) { LogError("Error in pthread_sigmask"); return EXIT_FAILURE; } - LogInfo("Start!"); - SecurityManager::SocketManager manager; + if (!MountNS::isMountNamespaceEnabled()) { + // Lagacy systems without mount namespace. + // In this case security-manager will run without WORKER + return security_manager(Channel()); + } - if (!REGISTER_SOCKET_SERVICE(manager, SecurityManager::Service)) - { - LogError("Unable to create socket service. Exiting."); + // New systems with mount namespace. + // In this case security-manager requires WORKER that will + // be able to manage with mount namespaces. + ChannelCreator creator; + + if (!creator.init()) { + LogError("Error creating channel"); return EXIT_FAILURE; } + Channel child(creator.child()); + Channel parent(creator.parent()); + + int ret = fork(); - manager.MainLoop(); + if (-1 == ret) { + LogError("Error in fork"); + return EXIT_FAILURE; + } + + if (ret == 0) { + // child + parent.closeAll(); + return worker(std::move(child)); + } else { + // parent + child.closeAll(); + return security_manager(std::move(parent)); + } } catch (const SecurityManager::FileLocker::Exception::LockFailed &e) { LogError("Unable to get a file lock. Exiting."); return EXIT_FAILURE; diff --git a/src/server/main/socket-manager.cpp b/src/server/main/socket-manager.cpp index ef69a15..885b7e3 100644 --- a/src/server/main/socket-manager.cpp +++ b/src/server/main/socket-manager.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,7 @@ struct SignalService : public GenericSocketService { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); if (-1 == pthread_sigmask(SIG_BLOCK, &mask, NULL)) return -1; return signalfd(-1, &mask, 0); @@ -104,6 +106,10 @@ struct SignalService : public GenericSocketService { if (manager) manager->MainLoopStop(); return; + } else if (siginfo->ssi_signo == SIGCHLD) { + int status; + waitpid(-1, &status, WNOHANG); + return; } LogInfo("This should not happend. Got signal: " << siginfo->ssi_signo); diff --git a/src/server/service/include/service.h b/src/server/service/include/service.h index 8c9e682..5181ff3 100644 --- a/src/server/service/include/service.h +++ b/src/server/service/include/service.h @@ -27,6 +27,7 @@ #include "base-service.h" #include "credentials.h" #include "service_impl.h" +#include namespace SecurityManager { @@ -43,7 +44,9 @@ class Service : public: Service(); ServiceDescriptionVector GetServiceDescription(); - + void RegisterChannel(Channel channel) { + serviceImpl.RegisterChannel(std::move(channel)); + } private: /** -- 2.7.4