selinux-access.c 12.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

  Copyright 2012 Dan Walsh

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include "selinux-access.h"

#ifdef HAVE_SELINUX

#include <stdio.h>
#include <string.h>
#include <errno.h>
29
#include <limits.h>
30
31
32
33
34
#include <selinux/selinux.h>
#include <selinux/avc.h>
#ifdef HAVE_AUDIT
#include <libaudit.h>
#endif
35
36
37
38
39
40
41
42
43
#include <dbus.h>

#include "util.h"
#include "log.h"
#include "bus-errors.h"
#include "dbus-common.h"
#include "audit.h"
#include "selinux-util.h"
#include "audit-fd.h"
44

45
static bool initialized = false;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

struct auditstruct {
        const char *path;
        char *cmdline;
        uid_t loginuid;
        uid_t uid;
        gid_t gid;
};

static int bus_get_selinux_security_context(
                DBusConnection *connection,
                const char *name,
                char **scon,
                DBusError *error) {

61
        _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
62
63
64
65
        DBusMessageIter iter, sub;
        const char *bytes;
        char *b;
        int nbytes;
66
67
68
69
70
71
72
73

        m = dbus_message_new_method_call(
                        DBUS_SERVICE_DBUS,
                        DBUS_PATH_DBUS,
                        DBUS_INTERFACE_DBUS,
                        "GetConnectionSELinuxSecurityContext");
        if (!m) {
                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
74
                return -ENOMEM;
75
76
        }

77
78
79
80
        if (!dbus_message_append_args(
                            m,
                            DBUS_TYPE_STRING, &name,
                            DBUS_TYPE_INVALID)) {
81
                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
82
                return -ENOMEM;
83
84
85
        }

        reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
86
87
        if (!reply)
                return -EIO;
88

89
90
        if (dbus_set_error_from_message(error, reply))
                return -EIO;
91

92
        if (!dbus_message_iter_init(reply, &iter))
93
                return -EIO;
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
        if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
                return -EIO;

        dbus_message_iter_recurse(&iter, &sub);
        dbus_message_iter_get_fixed_array(&sub, &bytes, &nbytes);

        b = strndup(bytes, nbytes);
        if (!b)
                return -ENOMEM;

        *scon = b;

        log_debug("GetConnectionSELinuxSecurityContext %s (pid %ld)", *scon, (long) bus_get_unix_process_id(connection, name, error));

109
        return 0;
110
111
112
113
114
115
116
117
118
}

static int bus_get_audit_data(
                DBusConnection *connection,
                const char *name,
                struct auditstruct *audit,
                DBusError *error) {

        pid_t pid;
119
        int r;
120

121
122
        pid = bus_get_unix_process_id(connection, name, error);
        if (pid <= 0)
123
                return -EIO;
124

125
126
127
        r = audit_loginuid_from_pid(pid, &audit->loginuid);
        if (r < 0)
                return r;
128

129
130
131
        r = get_process_uid(pid, &audit->uid);
        if (r < 0)
                return r;
132

133
134
135
        r = get_process_gid(pid, &audit->gid);
        if (r < 0)
                return r;
136

137
        r = get_process_cmdline(pid, 0, true, &audit->cmdline);
138
139
        if (r < 0)
                return r;
140

141
        return 0;
142
143
144
145
146
147
}

/*
   Any time an access gets denied this callback will be called
   with the aduit data.  We then need to just copy the audit data into the msgbuf.
*/
148
149
150
151
152
153
static int audit_callback(
                void *auditdata,
                security_class_t cls,
                char *msgbuf,
                size_t msgbufsize) {

154
        struct auditstruct *audit = (struct auditstruct *) auditdata;
155

156
        snprintf(msgbuf, msgbufsize,
157
                 "auid=%d uid=%d gid=%d%s%s%s%s%s%s",
Daniel J Walsh's avatar
Daniel J Walsh committed
158
                 audit->loginuid,
159
160
161
162
163
164
165
166
                 audit->uid,
                 audit->gid,
                 (audit->path ? " path=\"" : ""),
                 strempty(audit->path),
                 (audit->path ? "\"" : ""),
                 (audit->cmdline ? " cmdline=\"" : ""),
                 strempty(audit->cmdline),
                 (audit->cmdline ? "\"" : ""));
Daniel J Walsh's avatar
Daniel J Walsh committed
167

168
        msgbuf[msgbufsize-1] = 0;
Daniel J Walsh's avatar
Daniel J Walsh committed
169

170
171
172
173
174
175
176
177
178
        return 0;
}

/*
   Any time an access gets denied this callback will be called
   code copied from dbus. If audit is turned on the messages will go as
   user_avc's into the /var/log/audit/audit.log, otherwise they will be
   sent to syslog.
*/
179
static int log_callback(int type, const char *fmt, ...) {
180
181
182
        va_list ap;

        va_start(ap, fmt);
183

184
#ifdef HAVE_AUDIT
185
        if (get_audit_fd() >= 0) {
186
                char buf[LINE_MAX];
187
188

                vsnprintf(buf, sizeof(buf), fmt, ap);
189
                audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
Lukas Nykryn's avatar
Lukas Nykryn committed
190
                va_end(ap);
191

192
193
194
195
196
                return 0;
        }
#endif
        log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
        va_end(ap);
197

198
199
200
201
202
203
204
205
206
        return 0;
}

/*
   Function must be called once to initialize the SELinux AVC environment.
   Sets up callbacks.
   If you want to cleanup memory you should need to call selinux_access_finish.
*/
static int access_init(void) {
207
        int r;
208
209

        if (avc_open(NULL, 0)) {
210
                log_error("avc_open() failed: %m");
211
212
213
                return -errno;
        }

214
215
        selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
        selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
216

217
        if (security_getenforce() >= 0)
218
                return 0;
219

220
221
        r = -errno;
        avc_destroy();
222

223
224
225
        return r;
}

226
static int selinux_access_init(DBusError *error) {
227
228
        int r;

229
        if (initialized)
230
231
                return 0;

232
        if (use_selinux()) {
233
234
                r = access_init();
                if (r < 0) {
Daniel J Walsh's avatar
Daniel J Walsh committed
235
                        dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
236
237
238
239
                        return r;
                }
        }

240
        initialized = true;
241
242
243
        return 0;
}

244
245
246
247
248
249
250
251
void selinux_access_free(void) {
        if (!initialized)
                return;

        avc_destroy();
        initialized = false;
}

252
static int get_audit_data(
253
254
255
256
                DBusConnection *connection,
                DBusMessage *message,
                struct auditstruct *audit,
                DBusError *error) {
257
258

        const char *sender;
259
260
261
        int r, fd;
        struct ucred ucred;
        socklen_t len;
262
263

        sender = dbus_message_get_sender(message);
264
265
266
        if (sender)
                return bus_get_audit_data(connection, sender, audit, error);

267
268
        if (!dbus_connection_get_unix_fd(connection, &fd))
                return -EINVAL;
269

270
271
272
273
274
        r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
        if (r < 0) {
                log_error("Failed to determine peer credentials: %m");
                return -errno;
        }
275

276
277
        audit->uid = ucred.uid;
        audit->gid = ucred.gid;
278

279
280
281
        r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
        if (r < 0)
                return r;
282

283
        r = get_process_cmdline(ucred.pid, 0, true, &audit->cmdline);
284
285
        if (r < 0)
                return r;
286

287
        return 0;
288
289
290
291
292
293
294
}

/*
   This function returns the security context of the remote end of the dbus
   connections.  Whether it is on the bus or a local connection.
*/
static int get_calling_context(
295
296
297
298
                DBusConnection *connection,
                DBusMessage *message,
                security_context_t *scon,
                DBusError *error) {
299
300
301

        const char *sender;
        int r;
Daniel J Walsh's avatar
Daniel J Walsh committed
302
        int fd;
303
304
305
306
307
308
309
310
311

        /*
           If sender exists then
           if sender is NULL this indicates a local connection.  Grab the fd
           from dbus and do an getpeercon to peers process context
        */
        sender = dbus_message_get_sender(message);
        if (sender) {
                r = bus_get_selinux_security_context(connection, sender, scon, error);
312
313
                if (r >= 0)
                        return r;
314

315
                log_error("bus_get_selinux_security_context failed: %m");
316
                return r;
Daniel J Walsh's avatar
Daniel J Walsh committed
317
318
        }

319
        log_debug("SELinux No Sender");
320
        if (!dbus_connection_get_unix_fd(connection, &fd)) {
Daniel J Walsh's avatar
Daniel J Walsh committed
321
322
323
324
325
326
327
328
                log_error("bus_connection_get_unix_fd failed %m");
                return -EINVAL;
        }

        r = getpeercon(fd, scon);
        if (r < 0) {
                log_error("getpeercon failed %m");
                return -errno;
329
330
331
332
333
334
335
336
337
338
339
        }

        return 0;
}

/*
   This function communicates with the kernel to check whether or not it should
   allow the access.
   If the machine is in permissive mode it will return ok.  Audit messages will
   still be generated if the access would be denied in enforcing mode.
*/
340
int selinux_access_check(
341
342
343
344
345
346
347
                DBusConnection *connection,
                DBusMessage *message,
                const char *path,
                const char *permission,
                DBusError *error) {

        security_context_t scon = NULL, fcon = NULL;
348
349
350
        int r = 0;
        const char *tclass = NULL;
        struct auditstruct audit;
351

352
353
354
355
356
357
358
359
        assert(connection);
        assert(message);
        assert(permission);
        assert(error);

        if (!use_selinux())
                return 0;

360
361
362
363
        r = selinux_access_init(error);
        if (r < 0)
                return r;

364
365
        log_debug("SELinux access check for path=%s permission=%s", strna(path), permission);

366
367
        audit.uid = audit.loginuid = (uid_t) -1;
        audit.gid = (gid_t) -1;
368
369
370
371
        audit.cmdline = NULL;
        audit.path = path;

        r = get_calling_context(connection, message, &scon, error);
372
        if (r < 0) {
Daniel J Walsh's avatar
Daniel J Walsh committed
373
                log_error("Failed to get caller's security context on: %m");
374
                goto finish;
Daniel J Walsh's avatar
Daniel J Walsh committed
375
        }
376

377
378
379
380
381
        if (path) {
                tclass = "service";
                /* get the file context of the unit file */
                r = getfilecon(path, &fcon);
                if (r < 0) {
Daniel J Walsh's avatar
Daniel J Walsh committed
382
383
                        dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
                        r = -errno;
384
                        log_error("Failed to get security context on %s: %m",path);
385
386
387
388
389
390
391
                        goto finish;
                }

        } else {
                tclass = "system";
                r = getcon(&fcon);
                if (r < 0) {
Daniel J Walsh's avatar
Daniel J Walsh committed
392
393
394
                        dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
                        r = -errno;
                        log_error("Failed to get current process context on: %m");
395
396
397
398
399
400
                        goto finish;
                }
        }

        (void) get_audit_data(connection, message, &audit, error);

401
402
        errno = 0;
        r = selinux_check_access(scon, fcon, tclass, permission, &audit);
403
        if (r < 0) {
404
                dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
405
                r = -errno;
Daniel J Walsh's avatar
Daniel J Walsh committed
406
                log_error("SELinux policy denies access.");
407
408
        }

409
410
        log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, audit.cmdline, r);

411
412
413
414
415
finish:
        free(audit.cmdline);
        freecon(scon);
        freecon(fcon);

416
        if (r && security_getenforce() != 1) {
417
418
419
420
421
422
423
424
                dbus_error_init(error);
                r = 0;
        }

        return r;
}

#else
425

426
int selinux_access_check(
427
428
                DBusConnection *connection,
                DBusMessage *message,
429
                const char *path,
430
431
432
                const char *permission,
                DBusError *error) {

433
434
435
        return 0;
}

436
void selinux_access_free(void) {
437
}
438

439
#endif