90a6cfcf31d1d9341e78016922e4243b236b1ce1
[platform/core/security/vasum.git] / server / containers-manager.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Jan Olszak <j.olszak@samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author  Jan Olszak (j.olszak@samsung.com)
22  * @brief   Definition of the class for managing containers
23  */
24
25 #include "config.hpp"
26
27 #include "host-dbus-definitions.hpp"
28 #include "common-dbus-definitions.hpp"
29 #include "container-dbus-definitions.hpp"
30 #include "containers-manager.hpp"
31 #include "container-admin.hpp"
32 #include "exception.hpp"
33
34 #include "utils/paths.hpp"
35 #include "logger/logger.hpp"
36 #include "config/manager.hpp"
37 #include "dbus/exception.hpp"
38 #include "utils/fs.hpp"
39
40 #include <boost/filesystem.hpp>
41 #include <boost/regex.hpp>
42 #include <cassert>
43 #include <string>
44 #include <climits>
45
46
47 namespace security_containers {
48
49
50 namespace {
51
52 bool regexMatchVector(const std::string& str, const std::vector<boost::regex>& v)
53 {
54     for (const boost::regex& toMatch: v) {
55         if (boost::regex_match(str, toMatch)) {
56             return true;
57         }
58     }
59
60     return false;
61 }
62
63 const std::string HOST_ID = "host";
64
65 } // namespace
66
67 ContainersManager::ContainersManager(const std::string& managerConfigPath): mDetachOnExit(false)
68 {
69     LOGD("Instantiating ContainersManager object...");
70     config::loadFromFile(managerConfigPath, mConfig);
71
72     mProxyCallPolicy.reset(new ProxyCallPolicy(mConfig.proxyCallRules));
73
74     using namespace std::placeholders;
75     mHostConnection.setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
76                                               this, HOST_ID, _1, _2, _3, _4, _5, _6, _7));
77
78     mHostConnection.setGetContainerDbusesCallback(bind(
79                 &ContainersManager::handleGetContainerDbuses, this, _1));
80
81     mHostConnection.setGetContainerIdsCallback(bind(&ContainersManager::handleGetContainerIdsCall,
82                                                     this, _1));
83
84     mHostConnection.setGetActiveContainerIdCallback(bind(&ContainersManager::handleGetActiveContainerIdCall,
85                                                          this, _1));
86
87     mHostConnection.setSetActiveContainerCallback(bind(&ContainersManager::handleSetActiveContainerCall,
88                                                        this, _1, _2));
89
90     for (auto& containerConfig : mConfig.containerConfigs) {
91         std::string containerConfigPath;
92
93         if (containerConfig[0] == '/') {
94             containerConfigPath = containerConfig;
95         } else {
96             std::string baseConfigPath = utils::dirName(managerConfigPath);
97             containerConfigPath = utils::createFilePath(baseConfigPath, "/", containerConfig);
98         }
99
100         LOGD("Creating Container " << containerConfigPath);
101         std::unique_ptr<Container> c(new Container(containerConfigPath,
102                                                    mConfig.runMountPointPrefix));
103         const std::string id = c->getId();
104         if (id == HOST_ID) {
105             throw ContainerOperationException("Cannot use reserved container ID");
106         }
107
108         c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler,
109                                                  this, id, _1, _2));
110
111         c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler,
112                                       this, id));
113
114         c->setFileMoveRequestCallback(std::bind(&ContainersManager::handleContainerMoveFileRequest,
115                                                 this, id, _1, _2, _3));
116
117         c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
118                                      this, id, _1, _2, _3, _4, _5, _6, _7));
119
120         c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged,
121                                             this, id, _1));
122
123         mContainers.insert(ContainerMap::value_type(id, std::move(c)));
124     }
125
126     // check if default container exists, throw ContainerOperationException if not found
127     if (mContainers.find(mConfig.defaultId) == mContainers.end()) {
128         LOGE("Provided default container ID " << mConfig.defaultId << " is invalid.");
129         throw ContainerOperationException("Provided default container ID " + mConfig.defaultId +
130                                           " is invalid.");
131     }
132
133     LOGD("ContainersManager object instantiated");
134
135     if (mConfig.inputConfig.enabled) {
136         LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
137         mSwitchingSequenceMonitor.reset(
138                 new InputMonitor(mConfig.inputConfig,
139                                  std::bind(&ContainersManager::switchingSequenceMonitorNotify,
140                                            this)));
141     }
142 }
143
144 ContainersManager::~ContainersManager()
145 {
146     LOGD("Destroying ContainersManager object...");
147
148     if (!mDetachOnExit) {
149         try {
150             stopAll();
151         } catch (ServerException&) {
152             LOGE("Failed to stop all of the containers");
153         }
154     }
155
156     LOGD("ContainersManager object destroyed");
157 }
158
159 void ContainersManager::focus(const std::string& containerId)
160 {
161     /* try to access the object first to throw immediately if it doesn't exist */
162     ContainerMap::mapped_type& foregroundContainer = mContainers.at(containerId);
163
164     for (auto& container : mContainers) {
165         LOGD(container.second->getId() << ": being sent to background");
166         container.second->goBackground();
167     }
168     mConfig.foregroundId = foregroundContainer->getId();
169     LOGD(mConfig.foregroundId << ": being sent to foreground");
170     foregroundContainer->goForeground();
171 }
172
173 void ContainersManager::startAll()
174 {
175     LOGI("Starting all containers");
176
177     bool isForegroundFound = false;
178
179     for (auto& container : mContainers) {
180         container.second->start();
181
182         if (container.first == mConfig.foregroundId) {
183             isForegroundFound = true;
184             LOGI(container.second->getId() << ": set as the foreground container");
185             container.second->goForeground();
186         }
187     }
188
189     if (!isForegroundFound) {
190         auto foregroundIterator = std::min_element(mContainers.begin(), mContainers.end(),
191                                                    [](ContainerMap::value_type &c1, ContainerMap::value_type &c2) {
192                                                        return c1.second->getPrivilege() < c2.second->getPrivilege();
193                                                    });
194
195         mConfig.foregroundId = foregroundIterator->second->getId();
196         LOGI(mConfig.foregroundId << ": no foreground container configured, setting one with highest priority");
197         foregroundIterator->second->goForeground();
198     }
199 }
200
201 void ContainersManager::stopAll()
202 {
203     LOGI("Stopping all containers");
204
205     for (auto& container : mContainers) {
206         container.second->stop();
207     }
208 }
209
210 std::string ContainersManager::getRunningForegroundContainerId()
211 {
212     for (auto& container : mContainers) {
213         if (container.first == mConfig.foregroundId &&
214             container.second->isRunning()) {
215             return container.first;
216         }
217     }
218     return std::string();
219 }
220
221 void ContainersManager::switchingSequenceMonitorNotify()
222 {
223     LOGI("switchingSequenceMonitorNotify() called");
224     // TODO: implement
225 }
226
227
228 void ContainersManager::setContainersDetachOnExit()
229 {
230     mDetachOnExit = true;
231
232     for (auto& container : mContainers) {
233         container.second->setDetachOnExit();
234     }
235 }
236
237 void ContainersManager::notifyActiveContainerHandler(const std::string& caller,
238                                                      const std::string& application,
239                                                      const std::string& message)
240 {
241     LOGI("notifyActiveContainerHandler(" << caller << ", " << application << ", " << message
242          << ") called");
243     try {
244         const std::string activeContainer = getRunningForegroundContainerId();
245         if (!activeContainer.empty() && caller != activeContainer) {
246             mContainers[activeContainer]->sendNotification(caller, application, message);
247         }
248     } catch(const SecurityContainersException&) {
249         LOGE("Notification from " << caller << " hasn't been sent");
250     }
251 }
252
253 void ContainersManager::displayOffHandler(const std::string& /*caller*/)
254 {
255     // get config of currently set container and switch if switchToDefaultAfterTimeout is true
256     const std::string activeContainerName = getRunningForegroundContainerId();
257     const auto& activeContainer = mContainers.find(activeContainerName);
258
259     if (activeContainer != mContainers.end() &&
260         activeContainer->second->isSwitchToDefaultAfterTimeoutAllowed()) {
261         LOGI("Switching to default container " << mConfig.defaultId);
262         focus(mConfig.defaultId);
263     }
264 }
265
266 void ContainersManager::handleContainerMoveFileRequest(const std::string& srcContainerId,
267                                                        const std::string& dstContainerId,
268                                                        const std::string& path,
269                                                        dbus::MethodResultBuilder::Pointer result)
270 {
271     // TODO: this implementation is only a placeholder.
272     // There are too many unanswered questions and security concerns:
273     // 1. What about mount namespace, host might not see the source/destination
274     //    file. The file might be a different file from a host perspective.
275     // 2. Copy vs move (speed and security concerns over already opened FDs)
276     // 3. Access to source and destination files - DAC, uid/gig
277     // 4. Access to source and destintation files - MAC, smack
278     // 5. Destination file uid/gid assignment
279     // 6. Destination file smack label assignment
280     // 7. Verifiability of the source path
281
282     // NOTE: other possible implementations include:
283     // 1. Sending file descriptors opened directly in each container through DBUS
284     //    using something like g_dbus_message_set_unix_fd_list()
285     // 2. SCS forking and calling setns(MNT) in each container and opening files
286     //    by itself, then passing FDs to the main process
287     // Now when the main process has obtained FDs (by either of those methods)
288     // it can do the copying by itself.
289
290     LOGI("File move requested\n"
291          << "src: " << srcContainerId << "\n"
292          << "dst: " << dstContainerId << "\n"
293          << "path: " << path);
294
295     ContainerMap::const_iterator srcIter = mContainers.find(srcContainerId);
296     if (srcIter == mContainers.end()) {
297         LOGE("Source container '" << srcContainerId << "' not found");
298         return;
299     }
300     Container& srcContainer = *srcIter->second;
301
302     ContainerMap::const_iterator dstIter = mContainers.find(dstContainerId);
303     if (dstIter == mContainers.end()) {
304         LOGE("Destination container '" << dstContainerId << "' not found");
305         result->set(g_variant_new("(s)", api::container::FILE_MOVE_DESTINATION_NOT_FOUND.c_str()));
306         return;
307     }
308     Container& dstContanier = *dstIter->second;
309
310     if (srcContainerId == dstContainerId) {
311         LOGE("Cannot send a file to yourself");
312         result->set(g_variant_new("(s)", api::container::FILE_MOVE_WRONG_DESTINATION.c_str()));
313         return;
314     }
315
316     if (!regexMatchVector(path, srcContainer.getPermittedToSend())) {
317         LOGE("Source container has no permissions to send the file: " << path);
318         result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_SEND.c_str()));
319         return;
320     }
321
322     if (!regexMatchVector(path, dstContanier.getPermittedToRecv())) {
323         LOGE("Destination container has no permissions to receive the file: " << path);
324         result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_RECEIVE.c_str()));
325         return;
326     }
327
328     namespace fs = boost::filesystem;
329     std::string srcPath = fs::absolute(srcContainerId, mConfig.containersPath).string() + path;
330     std::string dstPath = fs::absolute(dstContainerId, mConfig.containersPath).string() + path;
331
332     if (!utils::moveFile(srcPath, dstPath)) {
333         LOGE("Failed to move the file: " << path);
334         result->set(g_variant_new("(s)", api::container::FILE_MOVE_FAILED.c_str()));
335     } else {
336         result->set(g_variant_new("(s)", api::container::FILE_MOVE_SUCCEEDED.c_str()));
337         try {
338             dstContanier.sendNotification(srcContainerId, path, api::container::FILE_MOVE_SUCCEEDED);
339         } catch (ServerException&) {
340             LOGE("Notification to '" << dstContainerId << "' has not been sent");
341         }
342     }
343 }
344
345 void ContainersManager::handleProxyCall(const std::string& caller,
346                                         const std::string& target,
347                                         const std::string& targetBusName,
348                                         const std::string& targetObjectPath,
349                                         const std::string& targetInterface,
350                                         const std::string& targetMethod,
351                                         GVariant* parameters,
352                                         dbus::MethodResultBuilder::Pointer result)
353 {
354     if (!mProxyCallPolicy->isProxyCallAllowed(caller,
355                                               target,
356                                               targetBusName,
357                                               targetObjectPath,
358                                               targetInterface,
359                                               targetMethod)) {
360         LOGW("Forbidden proxy call; " << caller << " -> " << target << "; " << targetBusName
361                 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
362         result->setError(api::ERROR_FORBIDDEN, "Proxy call forbidden");
363         return;
364     }
365
366     LOGI("Proxy call; " << caller << " -> " << target << "; " << targetBusName
367             << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
368
369     auto asyncResultCallback = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
370         try {
371             GVariant* targetResult = asyncMethodCallResult.get();
372             result->set(g_variant_new("(v)", targetResult));
373         } catch (dbus::DbusException& e) {
374             result->setError(api::ERROR_FORWARDED, e.what());
375         }
376     };
377
378     if (target == HOST_ID) {
379         mHostConnection.proxyCallAsync(targetBusName,
380                                        targetObjectPath,
381                                        targetInterface,
382                                        targetMethod,
383                                        parameters,
384                                        asyncResultCallback);
385         return;
386     }
387
388     ContainerMap::const_iterator targetIter = mContainers.find(target);
389     if (targetIter == mContainers.end()) {
390         LOGE("Target container '" << target << "' not found");
391         result->setError(api::ERROR_UNKNOWN_ID, "Unknown proxy call target");
392         return;
393     }
394
395     Container& targetContainer = *targetIter->second;
396     targetContainer.proxyCallAsync(targetBusName,
397                                    targetObjectPath,
398                                    targetInterface,
399                                    targetMethod,
400                                    parameters,
401                                    asyncResultCallback);
402 }
403
404 void ContainersManager::handleGetContainerDbuses(dbus::MethodResultBuilder::Pointer result)
405 {
406     std::vector<GVariant*> entries;
407     for (auto& container : mContainers) {
408         GVariant* containerId = g_variant_new_string(container.first.c_str());
409         GVariant* dbusAddress = g_variant_new_string(container.second->getDbusAddress().c_str());
410         GVariant* entry = g_variant_new_dict_entry(containerId, dbusAddress);
411         entries.push_back(entry);
412     }
413     GVariant* dict = g_variant_new_array(G_VARIANT_TYPE("{ss}"), entries.data(), entries.size());
414     result->set(g_variant_new("(*)", dict));
415 }
416
417 void ContainersManager::handleDbusStateChanged(const std::string& containerId,
418                                                const std::string& dbusAddress)
419 {
420     mHostConnection.signalContainerDbusState(containerId, dbusAddress);
421 }
422
423 void ContainersManager::handleGetContainerIdsCall(dbus::MethodResultBuilder::Pointer result)
424 {
425     std::vector<GVariant*> containerIds;
426     for(auto& container: mContainers){
427         containerIds.push_back(g_variant_new_string(container.first.c_str()));
428     }
429
430     GVariant* array = g_variant_new_array(G_VARIANT_TYPE("s"),
431                                           containerIds.data(),
432                                           containerIds.size());
433     result->set(g_variant_new("(*)", array));
434 }
435
436 void ContainersManager::handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result)
437 {
438     LOGI("GetActiveContainerId call");
439     if (mContainers[mConfig.foregroundId]->isRunning()){
440         result->set(g_variant_new("(s)", mConfig.foregroundId.c_str()));
441     } else {
442         result->set(g_variant_new("(s)", ""));
443     }
444 }
445
446 void ContainersManager::handleSetActiveContainerCall(const std::string& id,
447                                                      dbus::MethodResultBuilder::Pointer result)
448 {
449     LOGI("SetActiveContainer call; Id=" << id );
450     auto container = mContainers.find(id);
451     if (container == mContainers.end()){
452         LOGE("No container with id=" << id );
453         result->setError(api::ERROR_UNKNOWN_ID, "No such container id");
454         return;
455     }
456
457     if (container->second->isStopped()){
458         LOGE("Could not activate a stopped container");
459         result->setError(api::host::ERROR_CONTAINER_STOPPED,
460                          "Could not activate a stopped container");
461         return;
462     }
463
464     focus(id);
465     result->setVoid();
466 }
467
468 } // namespace security_containers