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