chiark / gitweb /
selinux: fix various problems
[elogind.git] / src / core / selinux-access.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Dan Walsh
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "util.h"
23 #include "job.h"
24 #include "manager.h"
25 #include "selinux-access.h"
26
27 #ifdef HAVE_SELINUX
28 #include "dbus.h"
29 #include "log.h"
30 #include "dbus-unit.h"
31 #include "bus-errors.h"
32 #include "dbus-common.h"
33 #include "audit.h"
34
35 #include <stdio.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <selinux/selinux.h>
39 #include <selinux/avc.h>
40 #ifdef HAVE_AUDIT
41 #include <libaudit.h>
42 #endif
43 #include <limits.h>
44
45 /* FD to send audit messages to */
46 static int audit_fd = -1;
47 static int selinux_enabled = -1;
48 static int first_time = 1;
49 static int selinux_enforcing = 0;
50
51 struct auditstruct {
52         const char *path;
53         char *cmdline;
54         uid_t loginuid;
55         uid_t uid;
56         gid_t gid;
57 };
58
59 /*
60    Define a mapping between the systemd method calls and the SELinux access to check.
61    We define two tables, one for access checks on unit files, and one for
62    access checks for the system in general.
63
64    If we do not find a match in either table, then the "undefined" system
65    check will be called.
66 */
67
68 static const char unit_methods[] =
69         "DisableUnitFiles\0"       "disable\0"
70         "EnableUnitFiles\0"        "enable\0"
71         "GetUnit\0"                "status\0"
72         "GetUnitFileState\0"       "status\0"
73         "Kill\0"                   "stop\0"
74         "KillUnit\0"               "stop\0"
75         "LinkUnitFiles\0"          "enable\0"
76         "MaskUnitFiles\0"          "disable\0"
77         "PresetUnitFiles\0"        "enable\0"
78         "ReenableUnitFiles\0"      "enable\0"
79         "ReloadOrRestart\0"        "start\0"
80         "ReloadOrRestartUnit\0"    "start\0"
81         "ReloadOrTryRestart\0"     "start\0"
82         "ReloadOrTryRestartUnit\0" "start\0"
83         "Reload\0"                 "reload\0"
84         "ReloadUnit\0"             "reload\0"
85         "ResetFailedUnit\0"        "stop\0"
86         "Restart\0"                "start\0"
87         "RestartUnit\0"            "start\0"
88         "Start\0"                  "start\0"
89         "StartUnit\0"              "start\0"
90         "StartUnitReplace\0"       "start\0"
91         "Stop\0"                   "stop\0"
92         "StopUnit\0"               "stop\0"
93         "TryRestart\0"             "start\0"
94         "TryRestartUnit\0"         "start\0"
95         "UnmaskUnitFiles\0"        "enable\0";
96
97 static const char system_methods[] =
98         "ClearJobs\0"              "reboot\0"
99         "CreateSnapshot\0"         "status\0"
100         "Dump\0"                   "status\0"
101         "Exit\0"                   "halt\0"
102         "FlushDevices\0"           "halt\0"
103         "Get\0"                    "status\0"
104         "GetAll\0"                 "status\0"
105         "GetJob\0"                 "status\0"
106         "GetSeat\0"                "status\0"
107         "GetSession\0"             "status\0"
108         "GetSessionByPID\0"        "status\0"
109         "GetUnitByPID\0"           "status\0"
110         "GetUser\0"                "status\0"
111         "Halt\0"                   "halt\0"
112         "Introspect\0"             "status\0"
113         "KExec\0"                  "reboot\0"
114         "KillSession\0"            "halt\0"
115         "KillUser\0"               "halt\0"
116         "LoadUnit\0"               "reload\0"
117         "ListJobs\0"               "status\0"
118         "ListSeats\0"              "status\0"
119         "ListSessions\0"           "status\0"
120         "ListUnits\0"              "status\0"
121         "ListUnitFiles\0"          "status\0"
122         "ListUsers\0"              "status\0"
123         "LockSession\0"            "halt\0"
124         "PowerOff\0"               "halt\0"
125         "Reboot\0"                 "reboot\0"
126         "Reload\0"                 "reload\0"
127         "Reexecute\0"              "reload\0"
128         "ResetFailed\0"            "reload\0"
129         "Subscribe\0"              "status\0"
130         "SwithcRoot\0"             "reboot\0"
131         "SetEnvironment\0"         "status\0"
132         "SetUserLinger\0"          "halt\0"
133         "TerminateSeat\0"          "halt\0"
134         "TerminateSession\0"       "halt\0"
135         "TerminateUser\0"          "halt\0"
136         "Unsubscribe\0"            "status\0"
137         "UnsetEnvironment\0"       "status\0"
138         "UnsetAndSetEnvironment\0" "status\0";
139
140 /*
141    If the admin toggles the selinux enforcment mode this callback
142    will get called before the next access check
143 */
144 static int setenforce_callback(int enforcing)
145 {
146         selinux_enforcing = enforcing;
147         return 0;
148 }
149
150 /* This mimics dbus_bus_get_unix_user() */
151 static int bus_get_selinux_security_context(
152                 DBusConnection *connection,
153                 const char *name,
154                 char **scon,
155                 DBusError *error) {
156
157         DBusMessage *m = NULL, *reply = NULL;
158         int r;
159
160         m = dbus_message_new_method_call(
161                         DBUS_SERVICE_DBUS,
162                         DBUS_PATH_DBUS,
163                         DBUS_INTERFACE_DBUS,
164                         "GetConnectionSELinuxSecurityContext");
165         if (!m) {
166                 r = -errno;
167                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
168                 goto finish;
169         }
170
171         r = dbus_message_append_args(
172                 m,
173                 DBUS_TYPE_STRING, &name,
174                 DBUS_TYPE_INVALID);
175         if (!r) {
176                 r = -errno;
177                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
178                 goto finish;
179         }
180
181         reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
182         if (!reply) {
183                 r = -errno;
184                 goto finish;
185         }
186
187         r = dbus_set_error_from_message(error, reply);
188         if (!r) {
189                 r = -errno;
190                 goto finish;
191         }
192
193         r = dbus_message_get_args(
194                 reply, error,
195                 DBUS_TYPE_STRING, scon,
196                 DBUS_TYPE_INVALID);
197         if (!r) {
198                 r = -errno;
199                 goto finish;
200         }
201
202         r = 0;
203 finish:
204         if (m)
205                 dbus_message_unref(m);
206
207         if (reply)
208                 dbus_message_unref(reply);
209
210         return r;
211 }
212
213 /* This mimics dbus_bus_get_unix_user() */
214 static int bus_get_audit_data(
215                 DBusConnection *connection,
216                 const char *name,
217                 struct auditstruct *audit,
218                 DBusError *error) {
219
220         pid_t pid;
221         int r;
222
223         pid = bus_get_unix_process_id(connection, name, error);
224         if (pid <= 0)
225                 return -EINVAL;
226
227         r = audit_loginuid_from_pid(pid, &audit->loginuid);
228         if (r < 0)
229                 return r;
230
231         r = get_process_uid(pid, &audit->uid);
232         if (r < 0)
233                 return r;
234
235         r = get_process_gid(pid, &audit->gid);
236         if (r < 0)
237                 return r;
238
239         r = get_process_cmdline(pid, LINE_MAX, true, &audit->cmdline);
240         if (r < 0)
241                 return r;
242
243         return 0;
244 }
245
246 /*
247    Any time an access gets denied this callback will be called
248    with the aduit data.  We then need to just copy the audit data into the msgbuf.
249 */
250 static int audit_callback(void *auditdata, security_class_t cls,
251                           char *msgbuf, size_t msgbufsize)
252 {
253         struct auditstruct *audit = (struct auditstruct *) auditdata;
254         snprintf(msgbuf, msgbufsize,
255                  "auid=%d uid=%d gid=%d",
256                  audit->loginuid,
257                  audit->uid, audit->gid);
258
259         if (audit->path) {
260                 strncat(msgbuf," path=\"", msgbufsize);
261                 strncat(msgbuf, audit->path, msgbufsize);
262                 strncat(msgbuf,"\"", msgbufsize);
263         }
264
265         if (audit->cmdline) {
266                 strncat(msgbuf," cmdline=\"", msgbufsize);
267                 strncat(msgbuf, audit->cmdline, msgbufsize);
268                 strncat(msgbuf,"\"", msgbufsize);
269         }
270
271         return 0;
272 }
273
274 /*
275    Any time an access gets denied this callback will be called
276    code copied from dbus. If audit is turned on the messages will go as
277    user_avc's into the /var/log/audit/audit.log, otherwise they will be
278    sent to syslog.
279 */
280 static int log_callback(int type, const char *fmt, ...)
281 {
282         va_list ap;
283
284         va_start(ap, fmt);
285 #ifdef HAVE_AUDIT
286         if (audit_fd >= 0) {
287                 char buf[LINE_MAX*2];
288
289                 vsnprintf(buf, sizeof(buf), fmt, ap);
290                 audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC,
291                                            buf, NULL, NULL, NULL, 0);
292                 va_end(ap);
293                 return 0;
294         }
295 #endif
296         log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
297         va_end(ap);
298         return 0;
299 }
300
301 /*
302    Function must be called once to initialize the SELinux AVC environment.
303    Sets up callbacks.
304    If you want to cleanup memory you should need to call selinux_access_finish.
305 */
306 static int access_init(void) {
307
308         int r = -1;
309
310         if (avc_open(NULL, 0)) {
311                 log_error("avc_open failed: %m");
312                 return -errno;
313         }
314
315         selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) &audit_callback);
316         selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback);
317         selinux_set_callback(SELINUX_CB_SETENFORCE, (union selinux_callback) &setenforce_callback);
318
319         if ((r = security_getenforce()) >= 0) {
320                 setenforce_callback(r);
321                 return 0;
322         }
323         r = -errno;
324         avc_destroy();
325         return r;
326 }
327
328 static int selinux_init(Manager *m, DBusError *error) {
329
330         int r;
331
332 #ifdef HAVE_AUDIT
333         audit_fd = m->audit_fd;
334 #endif
335         if (!first_time)
336                 return 0;
337
338         if (selinux_enabled < 0)
339                 selinux_enabled = is_selinux_enabled() == 1;
340
341         if (selinux_enabled) {
342                 /* if not first time is not set, then initialize access */
343                 r = access_init();
344                 if (r < 0) {
345                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
346                         return r;
347                 }
348         }
349
350         first_time = 0;
351         return 0;
352 }
353
354 static int get_audit_data(
355         DBusConnection *connection,
356         DBusMessage *message,
357         struct auditstruct *audit,
358         DBusError *error) {
359
360         const char *sender;
361         int r;
362
363         sender = dbus_message_get_sender(message);
364         if (sender)
365                 return bus_get_audit_data(connection, sender, audit, error);
366         else {
367                 int fd;
368                 struct ucred ucred;
369                 socklen_t len;
370
371                 if (!dbus_connection_get_unix_fd(connection, &fd))
372                         return -EINVAL;
373
374                 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
375                 if (r < 0) {
376                         log_error("Failed to determine peer credentials: %m");
377                         return -errno;
378                 }
379
380                 audit->uid = ucred.uid;
381                 audit->gid = ucred.gid;
382
383                 r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
384                 if (r < 0)
385                         return r;
386
387                 r = get_process_cmdline(ucred.pid, LINE_MAX, true, &audit->cmdline);
388                 if (r < 0)
389                         return r;
390
391                 return 0;
392         }
393 }
394
395 /*
396    This function returns the security context of the remote end of the dbus
397    connections.  Whether it is on the bus or a local connection.
398 */
399 static int get_calling_context(
400         DBusConnection *connection,
401         DBusMessage *message,
402         security_context_t *scon,
403         DBusError *error) {
404
405         const char *sender;
406         int r;
407         int fd;
408
409         /*
410            If sender exists then
411            if sender is NULL this indicates a local connection.  Grab the fd
412            from dbus and do an getpeercon to peers process context
413         */
414         sender = dbus_message_get_sender(message);
415         if (sender) {
416                 r = bus_get_selinux_security_context(connection, sender, scon, error);
417                 if (r == 0)
418                         return 0;
419
420                 log_debug("bus_get_selinux_security_context failed %m");
421         }
422
423         r = dbus_connection_get_unix_fd(connection, &fd);
424         if (! r) {
425                 log_error("bus_connection_get_unix_fd failed %m");
426                 return -EINVAL;
427         }
428
429         r = getpeercon(fd, scon);
430         if (r < 0) {
431                 log_error("getpeercon failed %m");
432                 return -errno;
433         }
434
435         return 0;
436 }
437
438 /*
439    This function returns the SELinux permission to check and whether or not the
440    check requires a unit file.
441 */
442 static void selinux_perm_lookup(const char *method, const char **perm, bool *require_unit) {
443         const char *m, *p;
444
445         NULSTR_FOREACH_PAIR(m, p, unit_methods)
446                 if (streq(method, m)) {
447                         *perm = p;
448                         *require_unit = true;
449                         return;
450                 }
451
452         NULSTR_FOREACH_PAIR(m, p, system_methods)
453                 if (streq(method, m)) {
454                         *perm = p;
455                         *require_unit = false;
456                         return;
457                 }
458
459         *require_unit = false;
460         *perm = "undefined";
461 }
462
463 /*
464    This function communicates with the kernel to check whether or not it should
465    allow the access.
466    If the machine is in permissive mode it will return ok.  Audit messages will
467    still be generated if the access would be denied in enforcing mode.
468 */
469 static int selinux_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error, const char *perm, const char *path) {
470         security_context_t scon = NULL;
471         security_context_t fcon = NULL;
472         int r = 0;
473         const char *tclass = NULL;
474         struct auditstruct audit;
475
476         audit.uid = audit.loginuid = (uid_t) -1;
477         audit.gid = (gid_t) -1;
478         audit.cmdline = NULL;
479         audit.path = path;
480
481         r = get_calling_context(connection, message, &scon, error);
482         if (r != 0) {
483                 log_error("Failed to get caller's security context on: %m");
484                 goto finish;
485         }
486         if (path) {
487                 tclass = "service";
488                 /* get the file context of the unit file */
489                 r = getfilecon(path, &fcon);
490                 if (r < 0) {
491                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
492                         r = -errno;
493                         log_error("Failed to get security context on: %s %m",path);
494                         goto finish;
495                 }
496
497         } else {
498                 tclass = "system";
499                 r = getcon(&fcon);
500                 if (r < 0) {
501                         dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
502                         r = -errno;
503                         log_error("Failed to get current process context on: %m");
504                         goto finish;
505                 }
506         }
507
508         (void) get_audit_data(connection, message, &audit, error);
509
510         errno= 0;
511         r = selinux_check_access(scon, fcon, tclass, perm, &audit);
512         if (r < 0) {
513                 r = -errno;
514                 log_error("SELinux policy denies access.");
515                 dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
516         }
517
518         log_debug("SELinux checkaccess scon %s tcon %s tclass %s perm %s path %s cmdline %s: %d", scon, fcon, tclass, perm, path, audit.cmdline, r);
519 finish:
520         free(audit.cmdline);
521         freecon(scon);
522         freecon(fcon);
523
524         return r;
525 }
526
527 /*
528   Clean up memory allocated in selinux_avc_init
529 */
530 void selinux_access_finish(void) {
531         if (!first_time)
532                 avc_destroy();
533         first_time = 1;
534 }
535
536 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
537         const char *perm;
538         bool require_unit;
539         const char *member;
540         int r;
541
542         log_debug("SELinux unit access check %s\n", path);
543         r = selinux_init(m, error);
544         if (r < 0)
545                 return r;
546
547         if (! selinux_enabled)
548                 return 0;
549
550         member = dbus_message_get_member(message);
551
552         selinux_perm_lookup(member, &perm, &require_unit);
553         log_debug("SELinux dbus-unit Look %s up perm %s require_unit %d", member, perm, require_unit);
554
555         r = selinux_access_check(connection, message, m, error, perm, path);
556
557         /* if SELinux is in permissive mode return 0 */
558         if (r && (security_getenforce() != 1 )) {
559                 dbus_error_init(error);
560                 r = 0;
561         }
562
563         return r;
564 }
565
566 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
567         int r = -1;
568         const char *member;
569         bool require_unit;
570         const char *perm;
571         char *path = NULL;
572
573         log_debug("SELinux manager access check\n");
574         r = selinux_init(m, error);
575         if (r < 0)
576                 return r;
577
578         if (! selinux_enabled)
579                 return 0;
580
581         member = dbus_message_get_member(message);
582
583         selinux_perm_lookup(member, &perm, &require_unit);
584         log_debug("SELinux dbus-manager Lookup %s perm %s require_unit %d", member, perm, require_unit);
585
586         if (require_unit) {
587                 const char *name, *smode, *old_name = NULL;
588                 Unit *u;
589
590                 if (! dbus_message_get_args(
591                             message,
592                             error,
593                             DBUS_TYPE_STRING, &old_name,
594                             DBUS_TYPE_STRING, &name,
595                             DBUS_TYPE_STRING, &smode,
596                             DBUS_TYPE_INVALID)) {
597                         dbus_error_init(error);
598                         if (!dbus_message_get_args(
599                                     message,
600                                     error,
601                                     DBUS_TYPE_STRING, &name,
602                                     DBUS_TYPE_STRING, &smode,
603                                     DBUS_TYPE_INVALID)) {
604                                 dbus_error_init(error);
605                                 if (!dbus_message_get_args(
606                                             message,
607                                             error,
608                                             DBUS_TYPE_STRING, &name,
609                                             DBUS_TYPE_INVALID)) {
610                                         r = -EINVAL;
611                                         /* This is broken for now, if I can not get a name
612                                            return success.
613                                         */
614                                         log_error("SELinux dbus-manager failed to find unit %m");
615                                         r = 0;
616                                         goto finish;
617                                 }
618                         }
619                 }
620
621                 log_debug("SELinux dbus-manager load unit %s", name);
622                 r = manager_load_unit(m, name, NULL, error, &u);
623                 if (r < 0) {
624                         log_error("Unit %s is not loaded.", name);
625                         /* This is broken for now, if I can not load a unit
626                            return success.
627                          */
628                         dbus_error_init(error);
629                         r = 0;
630                         goto finish;
631                 }
632
633                 path = u->source_path ? u->source_path : u->fragment_path;
634                 if (!path) {
635 //                      r = -1;
636                         log_error("Unit %s does not have path.", name);
637                         goto finish;
638                 }
639         }
640         r = selinux_access_check(connection, message, m, error, perm, path);
641
642 finish:
643         /* if SELinux is in permissive mode return 0 */
644         if (r && (security_getenforce() != 1 )) {
645                 dbus_error_init(error);
646                 r = 0;
647         }
648         return r;
649 }
650
651 #else
652 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
653         return 0;
654 }
655
656 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
657         return 0;
658 }
659
660 void selinux_access_finish(void) {
661 }
662 #endif