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