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