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