chiark / gitweb /
b207b0d32a62f5c387e1c7e7e6ee214c4ef29086
[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 * 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 /* This mimics dbus_bus_get_unix_user() */
216 static int bus_get_audit_data(
217                 DBusConnection *connection,
218                 const char *name,
219                 struct auditstruct *audit,
220                 DBusError *error) {
221
222         pid_t pid;
223         int r;
224
225         pid = bus_get_unix_process_id(connection, name, error);
226         if (pid <= 0)
227                 return -EINVAL;
228
229         r = audit_loginuid_from_pid(pid, &audit->loginuid);
230         if (r < 0)
231                 return r;
232
233         r = get_process_uid(pid, &audit->uid);
234         if (r < 0)
235                 return r;
236
237         r = get_process_gid(pid, &audit->gid);
238         if (r < 0)
239                 return r;
240
241         r = get_process_cmdline(pid, LINE_MAX, true, &audit->cmdline);
242         if (r < 0)
243                 return r;
244
245         return 0;
246 }
247
248 /*
249    Any time an access gets denied this callback will be called
250    with the aduit data.  We then need to just copy the audit data into the msgbuf.
251 */
252 static int audit_callback(void *auditdata, security_class_t cls,
253                           char *msgbuf, size_t msgbufsize)
254 {
255         struct auditstruct *audit = (struct auditstruct *) auditdata;
256         snprintf(msgbuf, msgbufsize,
257                  "name=\"%s\" cmdline=\"%s\" auid=%d uid=%d gid=%d",
258                  audit->path, audit->cmdline, audit->loginuid,
259                  audit->uid, audit->gid);
260         return 0;
261 }
262
263 /*
264    Any time an access gets denied this callback will be called
265    code copied from dbus. If audit is turned on the messages will go as
266    user_avc's into the /var/log/audit/audit.log, otherwise they will be
267    sent to syslog.
268 */
269 static int log_callback(int type, const char *fmt, ...)
270 {
271         va_list ap;
272
273         va_start(ap, fmt);
274 #ifdef HAVE_AUDIT
275         if (audit_fd >= 0) {
276                 char buf[LINE_MAX*2];
277
278                 vsnprintf(buf, sizeof(buf), fmt, ap);
279                 audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC,
280                                            buf, NULL, NULL, NULL, 0);
281                 return 0;
282         }
283 #endif
284         log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
285         va_end(ap);
286         return 0;
287 }
288
289 /*
290    Function must be called once to initialize the SELinux AVC environment.
291    Sets up callbacks.
292    If you want to cleanup memory you should need to call selinux_access_finish.
293 */
294 static int access_init(void) {
295
296         int r = -1;
297
298         if (avc_open(NULL, 0)) {
299                 log_full(LOG_ERR, "avc_open failed: %m\n");
300                 return -errno;
301         }
302
303         selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) &audit_callback);
304         selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback);
305         selinux_set_callback(SELINUX_CB_SETENFORCE, (union selinux_callback) &setenforce_callback);
306
307         if ((r = security_getenforce()) >= 0) {
308                 setenforce_callback(r);
309                 return 0;
310         }
311         r = -errno;
312         avc_destroy();
313         return r;
314 }
315
316 static int selinux_init(Manager *m, DBusError *error) {
317
318         int r;
319
320 #ifdef HAVE_AUDIT
321         audit_fd = m->audit_fd;
322 #endif
323         if (!first_time)
324                 return 0;
325
326         if (selinux_enabled < 0)
327                 selinux_enabled = is_selinux_enabled() == 1;
328
329         if (selinux_enabled) {
330                 /* if not first time is not set, then initialize access */
331                 r = access_init();
332                 if (r < 0) {
333                         dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "Unable to initialize SELinux.");
334
335                         return r;
336                 }
337                 first_time = 0;
338         }
339
340         return 0;
341 }
342
343 static int get_audit_data(
344         DBusConnection *connection,
345         DBusMessage *message,
346         struct auditstruct *audit,
347         DBusError *error) {
348
349         const char *sender;
350         int r;
351
352         sender = dbus_message_get_sender(message);
353         if (sender)
354                 return bus_get_audit_data(connection, sender, audit, error);
355         else {
356                 int fd;
357                 struct ucred ucred;
358                 socklen_t len;
359
360                 if (!dbus_connection_get_unix_fd(connection, &fd))
361                         return -EINVAL;
362
363                 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
364                 if (r < 0) {
365                         log_error("Failed to determine peer credentials: %m");
366                         return -errno;
367                 }
368
369                 audit->uid = ucred.uid;
370                 audit->gid = ucred.gid;
371
372                 r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
373                 if (r < 0)
374                         return r;
375
376                 r = get_process_cmdline(ucred.pid, LINE_MAX, true, &audit->cmdline);
377                 if (r < 0)
378                         return r;
379
380                 return 0;
381         }
382 }
383
384 /*
385    This function returns the security context of the remote end of the dbus
386    connections.  Whether it is on the bus or a local connection.
387 */
388 static int get_calling_context(
389         DBusConnection *connection,
390         DBusMessage *message,
391         security_context_t *scon,
392         DBusError *error) {
393
394         const char *sender;
395         int r;
396
397         /*
398            If sender exists then
399            if sender is NULL this indicates a local connection.  Grab the fd
400            from dbus and do an getpeercon to peers process context
401         */
402         sender = dbus_message_get_sender(message);
403         if (sender) {
404                 r = bus_get_selinux_security_context(connection, sender, scon, error);
405                 if (r < 0)
406                         return -EINVAL;
407         } else {
408                 int fd;
409                 r = dbus_connection_get_unix_fd(connection, &fd);
410                 if (! r)
411                         return -EINVAL;
412
413                 r = getpeercon(fd, scon);
414                 if (r < 0)
415                         return -errno;
416         }
417
418         return 0;
419 }
420
421 /*
422    This function returns the SELinux permission to check and whether or not the
423    check requires a unit file.
424 */
425 static void selinux_perm_lookup(const char *method, const char **perm, int *require_unit)
426 {
427         int i;
428         *require_unit = -1;
429
430         for (i = 0; unit_methods[i][0]; i++) {
431                 if (streq(method, unit_methods[i][0])) {
432                         *perm = unit_methods[i][1];
433                         *require_unit = 1;
434                         break;
435                 }
436         }
437
438         if (*require_unit < 0) {
439                 for (i = 0; system_methods[i][0]; i++) {
440                         if (streq(method, system_methods[i][0])) {
441                                 *perm = system_methods[i][1];
442                                 *require_unit = 0;
443                                 break;
444                         }
445                 }
446         }
447         if (*require_unit < 0) {
448                 *require_unit = 0;
449                 *perm = "undefined";
450         }
451 }
452
453 /*
454    This function communicates with the kernel to check whether or not it should
455    allow the access.
456    If the machine is in permissive mode it will return ok.  Audit messages will
457    still be generated if the access would be denied in enforcing mode.
458 */
459 static int selinux_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error, const char *perm, const char *path) {
460         security_context_t scon = NULL;
461         security_context_t fcon = NULL;
462         int r = 0;
463         const char *tclass = NULL;
464         struct auditstruct audit;
465
466         audit.uid = audit.loginuid = (uid_t) -1;
467         audit.gid = (gid_t) -1;
468         audit.cmdline = NULL;
469         audit.path = path;
470
471         r = get_calling_context(connection, message, &scon, error);
472         if (r != 0)
473                 goto finish;
474
475         if (path) {
476                 tclass = "service";
477                 /* get the file context of the unit file */
478                 r = getfilecon(path, &fcon);
479                 if (r < 0) {
480                         log_full(LOG_ERR, "Failed to get security context on: %s %m\n",path);
481                         goto finish;
482                 }
483
484         } else {
485                 tclass = "system";
486                 r = getcon(&fcon);
487                 if (r < 0) {
488                         dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "Unable to get current context, SELinux policy denies access.");
489                         goto finish;
490                 }
491         }
492
493         (void) get_audit_data(connection, message, &audit, error);
494
495         errno= 0;
496         r = selinux_check_access(scon, fcon, tclass, perm, &audit);
497         if (r < 0) {
498                 r = -errno;
499                 log_error("SELinux Denied \"%s\"", audit.cmdline);
500
501                 dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
502         }
503
504         log_debug("SELinux checkaccess scon %s tcon %s tclass %s perm %s path %s: %d", scon, fcon, tclass, perm, path, r);
505 finish:
506         if (r)
507                 r = -errno;
508
509         free(audit.cmdline);
510         freecon(scon);
511         freecon(fcon);
512
513         return r;
514 }
515
516 /*
517   Clean up memory allocated in selinux_avc_init
518 */
519 void selinux_access_finish(void) {
520         if (!first_time)
521                 avc_destroy();
522         first_time = 1;
523 }
524
525 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
526         const char *perm;
527         int require_unit;
528         const char *member;
529         int r;
530
531         r = selinux_init(m, error);
532         if (r < 0)
533                 return r;
534
535         if (! selinux_enabled)
536                 return 0;
537
538         member = dbus_message_get_member(message);
539
540         selinux_perm_lookup(member, &perm, &require_unit);
541         log_debug("SELinux dbus-unit Look %s up perm %s require_unit %d", member, perm, require_unit);
542
543         r = selinux_access_check(connection, message, m, error, perm, path);
544         if (r < 0 && !selinux_enforcing) {
545                 dbus_error_init(error);
546                 r = 0;
547         }
548
549         return r;
550 }
551
552 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
553         int r = -1;
554         const char *member;
555         int require_unit;
556         const char *perm;
557         char *path = NULL;
558
559         r = selinux_init(m, error);
560         if (r < 0)
561                 return r;
562
563         if (! selinux_enabled)
564                 return 0;
565
566         member = dbus_message_get_member(message);
567
568         selinux_perm_lookup(member, &perm, &require_unit);
569         log_debug("SELinux dbus-manager Lookup %s perm %s require_unit %d", member, perm, require_unit);
570
571         if (require_unit) {
572                 const char *name;
573                 Unit *u;
574
575                 if (!dbus_message_get_args(
576                         message,
577                         error,
578                         DBUS_TYPE_STRING, &name,
579                         DBUS_TYPE_INVALID)) {
580                         r = -EINVAL;
581                         goto finish;
582                 }
583
584                 r = manager_load_unit(m, name, NULL, error, &u);
585                 if (r < 0) {
586                         dbus_set_error(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
587                         goto finish;
588                 }
589
590                 path = u->source_path ? u->source_path : u->fragment_path;
591         }
592         r = selinux_access_check(connection, message, m, error, perm, path);
593
594 finish:
595         /* if SELinux is in permissive mode return 0 */
596         if (r && (!selinux_enforcing)) {
597                 dbus_error_init(error);
598                 r = 0;
599         }
600         return r;
601 }
602
603 #else
604 int selinux_unit_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, const char *path, DBusError *error) {
605         return 0;
606 }
607
608 int selinux_manager_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error) {
609         return 0;
610 }
611
612 void selinux_access_finish(void) {
613 }
614 #endif