]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/selinux-access.c
Merge pull request #11714 from poettering/final-news-241
[thirdparty/systemd.git] / src / core / selinux-access.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "selinux-access.h"
4
5 #if HAVE_SELINUX
6
7 #include <errno.h>
8 #include <selinux/avc.h>
9 #include <selinux/selinux.h>
10 #include <stdio.h>
11 #if HAVE_AUDIT
12 #include <libaudit.h>
13 #endif
14
15 #include "sd-bus.h"
16
17 #include "alloc-util.h"
18 #include "audit-fd.h"
19 #include "bus-util.h"
20 #include "log.h"
21 #include "path-util.h"
22 #include "selinux-util.h"
23 #include "stdio-util.h"
24 #include "strv.h"
25 #include "util.h"
26
27 static bool initialized = false;
28
29 struct audit_info {
30 sd_bus_creds *creds;
31 const char *path;
32 const char *cmdline;
33 };
34
35 /*
36 Any time an access gets denied this callback will be called
37 with the audit data. We then need to just copy the audit data into the msgbuf.
38 */
39 static int audit_callback(
40 void *auditdata,
41 security_class_t cls,
42 char *msgbuf,
43 size_t msgbufsize) {
44
45 const struct audit_info *audit = auditdata;
46 uid_t uid = 0, login_uid = 0;
47 gid_t gid = 0;
48 char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
49 char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
50 char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a";
51
52 if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0)
53 xsprintf(login_uid_buf, UID_FMT, login_uid);
54 if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0)
55 xsprintf(uid_buf, UID_FMT, uid);
56 if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0)
57 xsprintf(gid_buf, GID_FMT, gid);
58
59 snprintf(msgbuf, msgbufsize,
60 "auid=%s uid=%s gid=%s%s%s%s%s%s%s",
61 login_uid_buf, uid_buf, gid_buf,
62 audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "",
63 audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : "");
64
65 return 0;
66 }
67
68 static int callback_type_to_priority(int type) {
69 switch(type) {
70
71 case SELINUX_ERROR:
72 return LOG_ERR;
73
74 case SELINUX_WARNING:
75 return LOG_WARNING;
76
77 case SELINUX_INFO:
78 return LOG_INFO;
79
80 case SELINUX_AVC:
81 default:
82 return LOG_NOTICE;
83 }
84 }
85
86 /*
87 libselinux uses this callback when access gets denied or other
88 events happen. If audit is turned on, messages will be reported
89 using audit netlink, otherwise they will be logged using the usual
90 channels.
91
92 Code copied from dbus and modified.
93 */
94 _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
95 va_list ap;
96 const char *fmt2;
97
98 #if HAVE_AUDIT
99 int fd;
100
101 fd = get_audit_fd();
102
103 if (fd >= 0) {
104 _cleanup_free_ char *buf = NULL;
105 int r;
106
107 va_start(ap, fmt);
108 r = vasprintf(&buf, fmt, ap);
109 va_end(ap);
110
111 if (r >= 0) {
112 audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
113 return 0;
114 }
115 }
116 #endif
117
118 fmt2 = strjoina("selinux: ", fmt);
119
120 va_start(ap, fmt);
121 #pragma GCC diagnostic push
122 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
123 log_internalv(LOG_AUTH | callback_type_to_priority(type),
124 0, __FILE__, __LINE__, __FUNCTION__,
125 fmt2, ap);
126 #pragma GCC diagnostic pop
127 va_end(ap);
128
129 return 0;
130 }
131
132 static int access_init(sd_bus_error *error) {
133
134 if (!mac_selinux_use())
135 return 0;
136
137 if (initialized)
138 return 1;
139
140 if (avc_open(NULL, 0) != 0) {
141 int enforce, saved_errno = errno;
142
143 enforce = security_getenforce();
144 log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m");
145
146 /* If enforcement isn't on, then let's suppress this
147 * error, and just don't do any AVC checks. The
148 * warning we printed is hence all the admin will
149 * see. */
150 if (enforce == 0)
151 return 0;
152
153 /* Return an access denied error, if we couldn't load
154 * the AVC but enforcing mode was on, or we couldn't
155 * determine whether it is one. */
156 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno));
157 }
158
159 selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
160 selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
161
162 initialized = true;
163 return 1;
164 }
165
166 /*
167 This function communicates with the kernel to check whether or not it should
168 allow the access.
169 If the machine is in permissive mode it will return ok. Audit messages will
170 still be generated if the access would be denied in enforcing mode.
171 */
172 int mac_selinux_generic_access_check(
173 sd_bus_message *message,
174 const char *path,
175 const char *permission,
176 sd_bus_error *error) {
177
178 _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
179 const char *tclass = NULL, *scon = NULL;
180 struct audit_info audit_info = {};
181 _cleanup_free_ char *cl = NULL;
182 char *fcon = NULL;
183 char **cmdline = NULL;
184 int r = 0;
185
186 assert(message);
187 assert(permission);
188 assert(error);
189
190 r = access_init(error);
191 if (r <= 0)
192 return r;
193
194 r = sd_bus_query_sender_creds(
195 message,
196 SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|
197 SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID|
198 SD_BUS_CREDS_SELINUX_CONTEXT|
199 SD_BUS_CREDS_AUGMENT /* get more bits from /proc */,
200 &creds);
201 if (r < 0)
202 goto finish;
203
204 /* The SELinux context is something we really should have
205 * gotten directly from the message or sender, and not be an
206 * augmented field. If it was augmented we cannot use it for
207 * authorization, since this is racy and vulnerable. Let's add
208 * an extra check, just in case, even though this really
209 * shouldn't be possible. */
210 assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM);
211
212 r = sd_bus_creds_get_selinux_context(creds, &scon);
213 if (r < 0)
214 goto finish;
215
216 if (path) {
217 /* Get the file context of the unit file */
218
219 r = getfilecon_raw(path, &fcon);
220 if (r < 0) {
221 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
222 goto finish;
223 }
224
225 tclass = "service";
226 } else {
227 r = getcon_raw(&fcon);
228 if (r < 0) {
229 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
230 goto finish;
231 }
232
233 tclass = "system";
234 }
235
236 sd_bus_creds_get_cmdline(creds, &cmdline);
237 cl = strv_join(cmdline, " ");
238
239 audit_info.creds = creds;
240 audit_info.path = path;
241 audit_info.cmdline = cl;
242
243 r = selinux_check_access(scon, fcon, tclass, permission, &audit_info);
244 if (r < 0)
245 r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
246
247 log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r);
248
249 finish:
250 freecon(fcon);
251
252 if (r < 0 && security_getenforce() != 1) {
253 sd_bus_error_free(error);
254 r = 0;
255 }
256
257 return r;
258 }
259
260 #else
261
262 int mac_selinux_generic_access_check(
263 sd_bus_message *message,
264 const char *path,
265 const char *permission,
266 sd_bus_error *error) {
267
268 return 0;
269 }
270
271 #endif