]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/selinux-access.c
core: align table
[thirdparty/systemd.git] / src / core / selinux-access.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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
27 static bool initialized = false;
28
29 struct audit_info {
30 sd_bus_creds *creds;
31 const char *path;
32 const char *cmdline;
33 const char *function;
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 (void) snprintf(msgbuf, msgbufsize,
61 "auid=%s uid=%s gid=%s%s%s%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 audit->function ? " function=\"" : "", strempty(audit->function), audit->function ? "\"" : "");
66
67 return 0;
68 }
69
70 static int callback_type_to_priority(int type) {
71 switch (type) {
72
73 case SELINUX_ERROR:
74 return LOG_ERR;
75
76 case SELINUX_WARNING:
77 return LOG_WARNING;
78
79 case SELINUX_INFO:
80 return LOG_INFO;
81
82 case SELINUX_AVC:
83 default:
84 return LOG_NOTICE;
85 }
86 }
87
88 /*
89 libselinux uses this callback when access gets denied or other
90 events happen. If audit is turned on, messages will be reported
91 using audit netlink, otherwise they will be logged using the usual
92 channels.
93
94 Code copied from dbus and modified.
95 */
96 _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
97 va_list ap;
98 const char *fmt2;
99
100 #if HAVE_AUDIT
101 int fd;
102
103 fd = get_audit_fd();
104
105 if (fd >= 0) {
106 _cleanup_free_ char *buf = NULL;
107 int r;
108
109 va_start(ap, fmt);
110 r = vasprintf(&buf, fmt, ap);
111 va_end(ap);
112
113 if (r >= 0) {
114 if (type == SELINUX_AVC)
115 audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid());
116 else if (type == SELINUX_ERROR)
117 audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_SELINUX_ERR, buf, NULL, NULL, NULL, getuid());
118
119 return 0;
120 }
121 }
122 #endif
123
124 fmt2 = strjoina("selinux: ", fmt);
125
126 va_start(ap, fmt);
127
128 DISABLE_WARNING_FORMAT_NONLITERAL;
129 log_internalv(LOG_AUTH | callback_type_to_priority(type),
130 0, PROJECT_FILE, __LINE__, __func__,
131 fmt2, ap);
132 REENABLE_WARNING;
133 va_end(ap);
134
135 return 0;
136 }
137
138 static int access_init(sd_bus_error *error) {
139 int r;
140
141 if (!mac_selinux_use())
142 return 0;
143
144 if (initialized)
145 return 1;
146
147 if (avc_open(NULL, 0) != 0) {
148 r = -errno; /* Save original errno for later */
149
150 bool enforce = security_getenforce() != 0;
151 log_full_errno(enforce ? LOG_ERR : LOG_WARNING, r, "Failed to open the SELinux AVC: %m");
152
153 /* If enforcement isn't on, then let's suppress this error, and just don't do any AVC checks.
154 * The warning we printed is hence all the admin will see. */
155 if (!enforce)
156 return 0;
157
158 /* Return an access denied error based on the original errno, if we couldn't load the AVC but
159 * enforcing mode was on, or we couldn't determine whether it is one. */
160 errno = -r;
161 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %m");
162 }
163
164 selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) { .func_audit = audit_callback });
165 selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = 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_access_check_internal(
178 sd_bus_message *message,
179 const char *unit_path,
180 const char *unit_context,
181 const char *permission,
182 const char *function,
183 sd_bus_error *error) {
184
185 _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
186 const char *tclass, *scon, *acon;
187 _cleanup_free_ char *cl = NULL;
188 _cleanup_freecon_ char *fcon = NULL;
189 char **cmdline = NULL;
190 bool enforce;
191 int r = 0;
192
193 assert(message);
194 assert(permission);
195 assert(function);
196 assert(error);
197
198 r = access_init(error);
199 if (r <= 0)
200 return r;
201
202 /* delay call until we checked in `access_init()` if SELinux is actually enabled */
203 enforce = mac_selinux_enforcing();
204
205 r = sd_bus_query_sender_creds(
206 message,
207 SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|
208 SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID|
209 SD_BUS_CREDS_SELINUX_CONTEXT|
210 SD_BUS_CREDS_AUGMENT /* get more bits from /proc */,
211 &creds);
212 if (r < 0)
213 return r;
214
215 /* The SELinux context is something we really should have gotten directly from the message or sender,
216 * and not be an augmented field. If it was augmented we cannot use it for authorization, since this
217 * is racy and vulnerable. Let's add an extra check, just in case, even though this really shouldn't
218 * be possible. */
219 assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM);
220
221 r = sd_bus_creds_get_selinux_context(creds, &scon);
222 if (r < 0)
223 return r;
224
225 if (unit_context) {
226 /* Nice! The unit comes with a SELinux context read from the unit file */
227 acon = unit_context;
228 tclass = "service";
229 } else {
230 /* If no unit context is known, use our own */
231 if (getcon_raw(&fcon) < 0) {
232 log_warning_errno(errno, "SELinux getcon_raw() failed%s (perm=%s): %m",
233 enforce ? "" : ", ignoring",
234 permission);
235 if (!enforce)
236 return 0;
237
238 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context: %m");
239 }
240 if (!fcon) {
241 if (!enforce)
242 return 0;
243
244 return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "We appear not to have any SELinux context: %m");
245 }
246
247 acon = fcon;
248 tclass = "system";
249 }
250
251 (void) sd_bus_creds_get_cmdline(creds, &cmdline);
252 cl = strv_join(cmdline, " ");
253
254 struct audit_info audit_info = {
255 .creds = creds,
256 .path = unit_path,
257 .cmdline = cl,
258 .function = function,
259 };
260
261 r = selinux_check_access(scon, acon, tclass, permission, &audit_info);
262 if (r < 0) {
263 errno = -(r = errno_or_else(EPERM));
264
265 if (enforce)
266 sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access: %m");
267 }
268
269 log_full_errno_zerook(LOG_DEBUG, r,
270 "SELinux access check scon=%s tcon=%s tclass=%s perm=%s state=%s function=%s path=%s cmdline=%s: %m",
271 scon, acon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(unit_path), strna(empty_to_null(cl)));
272 return enforce ? r : 0;
273 }
274
275 #else /* HAVE_SELINUX */
276
277 int mac_selinux_access_check_internal(
278 sd_bus_message *message,
279 const char *unit_path,
280 const char *unit_label,
281 const char *permission,
282 const char *function,
283 sd_bus_error *error) {
284
285 return 0;
286 }
287
288 #endif /* HAVE_SELINUX */