e1975f8cad0c53eb940febeab1e0458708a5d0cb
[platform/core/system/libdbuspolicy.git] / src / libdbuspolicy1.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15 */
16
17
18 #include "internal/naive_policy_checker.hpp"
19 #include "internal/tslog.hpp"
20 #include "kdbus.h"
21 #include "libdbuspolicy1-private.h"
22
23 #include <assert.h>
24 #include <boost/utility/string_ref.hpp>
25 #include <cerrno>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <cstring>
29 #include <dbuspolicy1/libdbuspolicy1.h>
30 #include <fcntl.h>
31 #include <functional>
32 #include <grp.h>
33 #include <linux/kdbus.h>
34 #include <linux/limits.h>
35 #include <memory>
36 #include <mutex>
37 #include <new>
38 #include <pwd.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 #define DBUSPOLICY1_EXPORT __attribute__ ((visibility("default")))
44
45 using ldp_xml_parser::DecisionResult;
46 using ldp_xml_parser::MatchItemSend;
47 using ldp_xml_parser::MatchItemReceive;
48 using ldp_xml_parser::MatchItemOwn;
49 using ldp_xml_parser::NaivePolicyChecker;
50
51 typedef enum {
52         SYSTEM_BUS = 0,
53         SESSION_BUS = 1
54 } BusType;
55
56 /*
57  * This is a global structure that gets and keeps credentials from the current process,
58  * as they are needed for every policy checking operation.
59  */
60 struct udesc {
61         uid_t uid;
62         gid_t gid;
63         char label[256];
64
65         void init() {
66                 std::call_once(init_once_done, std::bind(&udesc::init_once, this));
67         }
68
69         void change_creds(uid_t uid_, gid_t gid_, const char *label_)
70         {
71                 uid = uid_;
72                 gid = gid_;
73                 if (label_) {
74                         if (sizeof(label) > strlen(label_))
75                                 memcpy(label, label_, strlen(label_) + 1);
76                         else
77                                 LOGE("Error: failed to strcpy, length of label(%s) exceed 255", label_);
78                 }
79         }
80
81 private:
82         std::once_flag init_once_done;
83         void init_once()
84         {
85                 char buf[256];
86                 int attr_fd;
87                 int r;
88
89                 attr_fd = open("/proc/self/attr/current", O_RDONLY);
90                 if (attr_fd < 0)
91                         assert(false && "failed open: /proc/self/attr/current");
92                 r = read(attr_fd, buf, sizeof(buf));
93                 close(attr_fd);
94
95                 if (r < 0 || r >= (long int)sizeof(label)) /* read */
96                         assert(false && "failed read: /proc/self/attr/current");
97                 buf[r] = 0;
98
99                 uid = getuid();
100                 gid = getgid();
101                 snprintf(label, r + 1 /* additional byte for \0 */, "%s", buf);
102
103                 tslog::init();
104         }
105 } g_udesc;
106
107 extern "C" void __dbuspolicy1_change_creds(uid_t uid, gid_t gid, const char* label)
108 {
109         g_udesc.change_creds(uid, gid, label);
110 }
111
112 /*
113  * This class decorates NaivePolicyChecker with initialization mechanisms.
114  * Thread-safety is ensured by C++ and static initialization in static member functions.
115  */
116 class Checker {
117 public:
118         Checker(NaivePolicyChecker &checker, const char *primary_file_name, const char *secondary_file_name)
119                 : _checker{checker}
120         {
121                 std::string primary_file_name_serialized = primary_file_name;
122                 primary_file_name_serialized.append(".serialized");
123
124                 auto ok = _checker.initDb(primary_file_name, primary_file_name_serialized.c_str());
125                 if (!ok)
126                         ok = _checker.initDb(secondary_file_name);
127
128                 assert(ok && "failed database initialization");
129
130                 _checker.updateGroupDb(g_udesc.uid, g_udesc.gid);
131         }
132
133         NaivePolicyChecker &checker() { return _checker; }
134
135         // this checks if this process can open a bus owned by bus_owner
136         inline bool can_open_owned_by(uid_t bus_owner) {
137                 return _checker.check(bus_owner, g_udesc.uid, g_udesc.gid, g_udesc.label) == DecisionResult::ALLOW;
138         }
139
140         static Checker &system() {
141                 static Checker checker{policy_checker_system(), system_bus_conf_file_primary(), SYSTEM_BUS_CONF_FILE_SECONDARY};
142                 return checker;
143         }
144
145         static Checker &session() {
146                 static Checker checker{policy_checker_session(), session_bus_conf_file_primary(), SESSION_BUS_CONF_FILE_SECONDARY};
147                 return checker;
148         }
149
150         static inline Checker &get(BusType bus_type) {
151                 if (SESSION_BUS == bus_type)
152                         return session();
153                 return system();
154         }
155
156 private:
157         NaivePolicyChecker &_checker;
158 };
159
160 /*
161  * kconn is a class which binds together a kdbus connection and a corresponding policy checker.
162  * It has two flavors:
163  * - global objects for system bus and session bus with private connections (static get() method);
164  * - objects created on demand when a client shares his kdbus connection with the library (static get_shared() method).
165  *
166  * Because  the pointer to such object is passed (through void *) to a client, and then clients
167  * pass the pointer to dbuspolicy1_* functions, we always need to maintain the same type,
168  * regardless of the flavor.
169  *
170  * Thread-safety is ensured by C++ and static initialization in static member functions.
171  */
172 class kconn {
173 public:
174         KdbusConnection conn;
175         NaivePolicyChecker &checker;
176         bool can_be_freed;
177
178         kconn(NaivePolicyChecker &ch, const char *resolved_path)
179                 : checker{ch}, can_be_freed{false}
180         {
181                 if (conn.connect(resolved_path, 0, _KDBUS_ATTACH_ALL, 0) < 0) {
182                         assert(false && "failed kdbus_connect");
183                 }
184         }
185
186         kconn(NaivePolicyChecker &ch, int fd)
187                 : checker{ch}, can_be_freed{true}
188         {
189                 conn.shared_init_fd(fd);
190         }
191
192         static inline kconn &get(BusType bus_type, const char *resolved_path) {
193                 if (SYSTEM_BUS == bus_type)
194                         return system(resolved_path);
195                 return session(resolved_path);
196         }
197
198         static inline kconn *get_shared(BusType bus_type, int fd)
199         {
200                 kconn *result = new (std::nothrow) kconn(Checker::get(bus_type).checker(), fd);
201                 if (nullptr == result)
202                         LOGE("Error: failed to allocate memory for policy configuration");
203                 return result;
204         }
205
206 private:
207         static kconn &system(const char *resolved_path) {
208                 static kconn c{Checker::system().checker(), resolved_path};
209                 return c;
210         }
211         static kconn &session(const char *resolved_path) {
212                 static kconn c{Checker::session().checker(), resolved_path};
213                 return c;
214         }
215
216 };
217
218 /*
219  * This class encapsulates functions used to analyze passed path to kdbus bus file.
220  * It recognizes system and session paths.
221  * If it recognizes path as a session bus, then it extracts owner's uid.
222  */
223 class BusPathResolver {
224         std::shared_ptr<const char> resolved_path;
225         BusType type{SESSION_BUS};
226         uid_t owner{0};
227         bool ok{false};
228
229         // We will never get 0 as a session bus owner (0 uses "0-system" bus),
230         // so it's OK to assume that bus_owner==0 always means error.
231         bool extract_bus_owner() noexcept
232         {
233                 boost::string_ref user_suffix("-user/bus");
234                 boost::string_ref ps(resolved_path.get());
235
236                 if (!ps.ends_with(user_suffix))
237                         return false;
238
239                 ps.remove_suffix(user_suffix.length());
240
241                 size_t last_slash = ps.find_last_of('/');
242                 if (last_slash == boost::string_ref::npos || last_slash == ps.length() - 1)
243                         return false;
244
245                 ps = ps.substr(last_slash+1);
246
247                 errno = 0;
248                 uid_t bus_owner = strtol(ps.data(), NULL, 10);
249                 if (errno)
250                         return false;
251
252                 owner = bus_owner;
253                 type = SESSION_BUS;
254                 return true;
255         }
256 #define STR_SIZE(x) (x), sizeof(x)-1
257         static constexpr boost::string_ref KDBUS_PATH_PREFIX{STR_SIZE("/sys/fs/kdbus/")};
258         static constexpr boost::string_ref KDBUS_SYSTEM_BUS_PATH{STR_SIZE("/sys/fs/kdbus/0-system/bus")};
259 #undef STR_SIZE
260
261 public:
262         BusPathResolver(const char *bus_path)
263                 : resolved_path(realpath(bus_path, nullptr), [](const char *a) { free(const_cast<char*>(a)); })
264         {
265                 if (!resolved_path)
266                         return;
267
268                 // recognize bus type
269                 if (KDBUS_SYSTEM_BUS_PATH == resolved_path.get()) {
270                         type = SYSTEM_BUS;
271                         ok = true;
272                 } else if (boost::string_ref(resolved_path.get()).starts_with(KDBUS_PATH_PREFIX)) {
273                         ok = extract_bus_owner();
274                 }
275         }
276         operator bool() { return ok; }
277         const char *path() { return resolved_path.get(); }
278         BusType bus_type() { return type; }
279         uid_t bus_owner() { return owner; }
280 };
281
282 constexpr boost::string_ref BusPathResolver::KDBUS_PATH_PREFIX;
283 constexpr boost::string_ref BusPathResolver::KDBUS_SYSTEM_BUS_PATH;
284
285 DBUSPOLICY1_EXPORT void* dbuspolicy1_init_shared(const char *bus_path, int fd)
286 {
287         assert(bus_path);
288
289         BusPathResolver path_resolver(bus_path);
290         if (!path_resolver) {
291                 LOGE("Error resolving bus path: %s", bus_path);
292                 return nullptr;
293         }
294
295         g_udesc.init();
296
297         auto bus_type = path_resolver.bus_type();
298
299         kconn *result = nullptr;
300         if (Checker::get(bus_type).can_open_owned_by(path_resolver.bus_owner())) {
301                 if (-1 == fd) {
302                         result = &kconn::get(bus_type, path_resolver.path());
303                 } else {
304                         result = kconn::get_shared(bus_type, fd);
305                 }
306         }
307
308         tslog::flush();
309
310         return result;
311 }
312
313 DBUSPOLICY1_EXPORT void* dbuspolicy1_init(const char *bus_path)
314 {
315         return dbuspolicy1_init_shared(bus_path, -1);
316 }
317
318 // helper functions
319 namespace {
320 kconn *KCONN(void *config) { return static_cast<kconn *>(config); }
321
322 /*
323  * This function prepares contents of info.names() for initialization of MatchItems.
324  * First, it tries to get names from kdbus connection.
325  * If it fails, names are added from the 'name' parameter, which is treaten
326  * as a string containing names separated by single space.
327  */
328 inline int prepareNames(const char *name, KdbusConnectionInfo &info,
329                 KdbusConnection::conn_info_type info_type) {
330         if (name && *name) {
331                 int r = info.get(name, info_type);
332                 if (r < 0)
333                         return r;
334
335                 if (info.names().empty())
336                         info.names().addSpaceSeparatedNames(name);
337         }
338         return 0;
339 }
340
341 constexpr int decisionToRetCode(DecisionResult decision) {
342         return static_cast<int>(decision);
343 }
344
345 constexpr ldp_xml_parser::MessageType toMessageType(int type) {
346         return static_cast<ldp_xml_parser::MessageType>(type);
347 }
348 } // namespace
349
350 DBUSPOLICY1_EXPORT void dbuspolicy1_init_set_pool(void *configuration, void *pool)
351 {
352         KCONN(configuration)->conn.shared_init_pool(pool);
353 }
354
355 DBUSPOLICY1_EXPORT void dbuspolicy1_free(void* configuration)
356 {
357         auto kconn = KCONN(configuration);
358         if (kconn->can_be_freed)
359                 delete kconn;
360 }
361
362 DBUSPOLICY1_EXPORT int dbuspolicy1_check_out(void* configuration,
363                                                                                          const char *destination,
364                                                                                          const char *sender,
365                                                                                          const char *path,
366                                                                                          const char *interface,
367                                                                                          const char *member,
368                                                                                          int         message_type,
369                                                                                          const char * /*error_name*/,
370                                                                                          int         /*reply_serial*/,
371                                                                                          int         /*requested_reply*/)
372 {
373         /* Broadcast signal has NULL destination */
374         /* Due to this sender can not check rule correctly */
375         if (message_type == DBUSPOLICY_MESSAGE_TYPE_SIGNAL && !destination)
376                 return 1;
377
378         tslog::LogLock log_lock;
379         auto kconn = KCONN(configuration);
380         KdbusConnectionInfo destinationInfo(kconn->conn);
381         int r;
382         /* check can send */
383         /* if broadcasting, then pass - null destination */
384         r = prepareNames(destination, destinationInfo, KdbusConnection::POLICY_CONN_INFO_ALL);
385         if (r < 0)
386                 return r;
387
388         auto m_type = toMessageType(message_type);
389
390         MatchItemSend sendItem(interface, member, path, m_type, destinationInfo.names());
391         auto decision = kconn->checker.check(g_udesc.uid, g_udesc.gid, g_udesc.label, sendItem);
392
393         if (DecisionResult::ALLOW != decision)
394                 return decisionToRetCode(decision);
395
396         KdbusConnectionInfo senderInfo(kconn->conn);
397         /* check can recv */
398         /* get sender information from kdbus */
399         r = prepareNames(sender, senderInfo, KdbusConnection::POLICY_CONN_INFO_NAME);
400         if (r < 0)
401                 return r;
402
403         MatchItemReceive receiveItem(interface, member, path, m_type, senderInfo.names());
404         return decisionToRetCode(kconn->checker.check(destinationInfo.uid(),
405                                                                                                   destinationInfo.gid(),
406                                                                                                   destinationInfo.label(),
407                                                                                                   receiveItem));
408 }
409
410 DBUSPOLICY1_EXPORT int dbuspolicy1_check_in(void* configuration,
411                                                                                         const char *destination,
412                                                                                         const char *sender,
413                                                                                         const char *sender_label,
414                                                                                         uid_t       sender_uid,
415                                                                                         gid_t       sender_gid,
416                                                                                         const char *path,
417                                                                                         const char *interface,
418                                                                                         const char *member,
419                                                                                         int         message_type,
420                                                                                         const char * /*error_name*/,
421                                                                                         int         /*reply_serial*/,
422                                                                                         int         /*requested_reply*/)
423 {
424         tslog::LogLock log_lock;
425         auto kconn = KCONN(configuration);
426         KdbusConnectionInfo info(kconn->conn);
427         int r;
428         r = prepareNames(destination, info, KdbusConnection::POLICY_CONN_INFO_NAME);
429         if (r < 0)
430                 return r;
431
432         auto m_type = toMessageType(message_type);
433
434         MatchItemSend sendItem(interface, member, path, m_type, info.names());
435         auto decision = kconn->checker.check(sender_uid, sender_gid, sender_label, sendItem);
436
437         if (DecisionResult::ALLOW != decision)
438                 return decisionToRetCode(decision);
439
440         if (!sender)
441                 sender = ":";
442
443         /* libdbus, gdbus pass multiple sender as parameter : eg. "name_A name_B name_C". */
444         KdbusBusNames names;
445         MatchItemReceive receiveItem(interface, member, path, m_type, names.addSpaceSeparatedNames(sender));
446
447         return decisionToRetCode(kconn->checker.check(g_udesc.uid,
448                                                                                                   g_udesc.gid,
449                                                                                                   g_udesc.label,
450                                                                                                   receiveItem));
451 }
452
453 DBUSPOLICY1_EXPORT int dbuspolicy1_can_own(void* configuration, const char* const service)
454 {
455         tslog::LogLock log_lock;
456         auto kconn = KCONN(configuration);
457         return decisionToRetCode(kconn->checker.check(g_udesc.uid,
458                                                                                                   g_udesc.gid,
459                                                                                                   g_udesc.label,
460                                                                                                   MatchItemOwn(service)));
461 }