1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2018-2023, Juniper Networks, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30
31 #include "opt_mac.h"
32
33 #include <sys/param.h>
34 #include <sys/capsicum.h>
35 #include <sys/proc.h>
36 #include <sys/vnode.h>
37 #include <sys/kernel.h>
38 #include <sys/module.h>
39 #include <sys/mac.h>
40 #include <sys/namei.h>
41 #include <sys/priv.h>
42 #include <sys/imgact.h>
43 #include <sys/sysctl.h>
44 #include <sys/syslog.h>
45 #include <security/mac/mac_policy.h>
46
47 #include "mac_grantbylabel.h"
48 #include <security/mac_veriexec/mac_veriexec_internal.h>
49
50 #define MAC_GRANTBYLABEL_FULLNAME "MAC/grantbylabel"
51
52 SYSCTL_DECL(_security_mac);
53 SYSCTL_NODE(_security_mac, OID_AUTO, grantbylabel, CTLFLAG_RW, 0,
54 "MAC/grantbylabel policy controls");
55
56 #ifdef MAC_DEBUG
57 static int mac_grantbylabel_debug;
58
59 SYSCTL_INT(_security_mac_grantbylabel, OID_AUTO, debug, CTLFLAG_RW,
60 &mac_grantbylabel_debug, 0, "Debug mac_grantbylabel");
61
62 #define GRANTBYLABEL_DEBUG(n, x) if (mac_grantbylabel_debug >= (n)) printf x
63
64 #define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...) \
65 do { \
66 GRANTBYLABEL_DEBUG((_lvl), (MAC_GRANTBYLABEL_FULLNAME ": " \
67 _fmt "\n", ##__VA_ARGS__)); \
68 } while(0)
69 #else
70 #define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...)
71 #endif
72
73
74 /* label token prefix */
75 #define GBL_PREFIX "gbl/"
76
77 static int mac_grantbylabel_slot;
78
79 #define SLOT(l) \
80 mac_label_get((l), mac_grantbylabel_slot)
81 #define SLOT_SET(l, v) \
82 mac_label_set((l), mac_grantbylabel_slot, (v))
83
84
85 /**
86 * @brief parse label into bitmask
87 *
88 * We are only interested in tokens prefixed by GBL_PREFIX ("gbl/").
89 *
90 * @return 32bit mask
91 */
92 static gbl_label_t
gbl_parse_label(const char * label)93 gbl_parse_label(const char *label)
94 {
95 gbl_label_t gbl;
96 char *cp;
97
98 if (!(label && *label))
99 return GBL_EMPTY;
100 gbl = 0;
101 for (cp = strstr(label, GBL_PREFIX); cp; cp = strstr(cp, GBL_PREFIX)) {
102 /* check we didn't find "fugbl/" */
103 if (cp > label && cp[-1] != ',') {
104 cp += sizeof(GBL_PREFIX);
105 continue;
106 }
107 cp += sizeof(GBL_PREFIX) - 1;
108 switch (*cp) {
109 case 'b':
110 if (strncmp(cp, "bind", 4) == 0)
111 gbl |= GBL_BIND;
112 break;
113 case 'd':
114 if (strncmp(cp, "daemon", 6) == 0)
115 gbl |= (GBL_BIND|GBL_IPC|GBL_NET|GBL_PROC|
116 GBL_SYSCTL|GBL_VACCESS);
117 break;
118 case 'i':
119 if (strncmp(cp, "ipc", 3) == 0)
120 gbl |= GBL_IPC;
121 break;
122 case 'k':
123 if (strncmp(cp, "kmem", 4) == 0)
124 gbl |= GBL_KMEM;
125 break;
126 case 'n':
127 if (strncmp(cp, "net", 3) == 0)
128 gbl |= GBL_NET;
129 break;
130 case 'p':
131 if (strncmp(cp, "proc", 4) == 0)
132 gbl |= GBL_PROC;
133 break;
134 case 'r':
135 if (strncmp(cp, "rtsock", 6) == 0)
136 gbl |= GBL_RTSOCK;
137 break;
138 case 's':
139 if (strncmp(cp, "sysctl", 6) == 0)
140 gbl |= GBL_SYSCTL;
141 break;
142 case 'v':
143 if (strncmp(cp, "vaccess", 7) == 0)
144 gbl |= GBL_VACCESS;
145 else if (strncmp(cp, "veriexec", 8) == 0)
146 gbl |= GBL_VERIEXEC;
147 break;
148 default: /* ignore unknown? */
149 MAC_GRANTBYLABEL_DBG(1,
150 "ignoring unknown token at %s/%s",
151 GBL_PREFIX, cp);
152 break;
153 }
154 }
155
156 return gbl;
157 }
158
159
160 /**
161 * @brief get the v_label for a vnode
162 *
163 * Lookup the label if not already set in v_label
164 *
165 * @return 32bit mask or 0 on error
166 */
167 static gbl_label_t
gbl_get_vlabel(struct vnode * vp,struct ucred * cred)168 gbl_get_vlabel(struct vnode *vp, struct ucred *cred)
169 {
170 struct vattr va;
171 const char *label;
172 gbl_label_t gbl;
173 int error;
174
175 gbl = SLOT(vp->v_label);
176 if (gbl == 0) {
177 error = VOP_GETATTR(vp, &va, cred);
178 if (error == 0) {
179 label = mac_veriexec_metadata_get_file_label(va.va_fsid,
180 va.va_fileid, va.va_gen, FALSE);
181 if (label) {
182 MAC_GRANTBYLABEL_DBG(1,
183 "label=%s dev=%ju, file %ju.%lu",
184 label,
185 (uintmax_t)va.va_fsid,
186 (uintmax_t)va.va_fileid,
187 va.va_gen);
188 gbl = gbl_parse_label(label);
189 } else {
190 gbl = GBL_EMPTY;
191 MAC_GRANTBYLABEL_DBG(2, "no label dev=%ju, file %ju.%lu",
192 (uintmax_t)va.va_fsid,
193 (uintmax_t)va.va_fileid,
194 va.va_gen);
195 }
196 }
197 }
198 return gbl;
199 }
200
201
202 /**
203 * @brief grant priv if warranted
204 *
205 * If the cred is root, we have nothing to do.
206 * Otherwise see if the current process has a label
207 * that grants it the requested priv.
208 */
209 static int
mac_grantbylabel_priv_grant(struct ucred * cred,int priv)210 mac_grantbylabel_priv_grant(struct ucred *cred, int priv)
211 {
212 gbl_label_t label;
213 int rc;
214
215 rc = EPERM; /* default response */
216
217 if ((curproc->p_flag & (P_KPROC|P_SYSTEM)))
218 return rc; /* not interested */
219
220 switch (priv) {
221 case PRIV_PROC_MEM_WRITE:
222 case PRIV_KMEM_READ:
223 case PRIV_KMEM_WRITE:
224 break;
225 case PRIV_VERIEXEC_DIRECT:
226 case PRIV_VERIEXEC_NOVERIFY:
227 /* XXX might want to skip in FIPS mode */
228 break;
229 default:
230 if (cred->cr_uid == 0)
231 return rc; /* not interested */
232 break;
233 }
234
235 label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) |
236 SLOT(curproc->p_label));
237
238 /*
239 * We look at the extra privs granted
240 * via process label.
241 */
242 switch (priv) {
243 case PRIV_IPC_READ:
244 case PRIV_IPC_WRITE:
245 if (label & GBL_IPC)
246 rc = 0;
247 break;
248 case PRIV_PROC_MEM_WRITE:
249 case PRIV_KMEM_READ:
250 case PRIV_KMEM_WRITE:
251 if (label & GBL_KMEM)
252 rc = 0;
253 break;
254 case PRIV_NETINET_BINDANY:
255 case PRIV_NETINET_RESERVEDPORT: /* socket bind low port */
256 case PRIV_NETINET_REUSEPORT:
257 if (label & GBL_BIND)
258 rc = 0;
259 break;
260 case PRIV_NETINET_ADDRCTRL6:
261 case PRIV_NET_LAGG:
262 case PRIV_NET_SETIFFIB:
263 case PRIV_NET_SETIFVNET:
264 case PRIV_NETINET_SETHDROPTS:
265 case PRIV_NET_VXLAN:
266 case PRIV_NETINET_GETCRED:
267 case PRIV_NETINET_IPSEC:
268 case PRIV_NETINET_RAW:
269 if (label & GBL_NET)
270 rc = 0;
271 break;
272 case PRIV_NETINET_MROUTE:
273 case PRIV_NET_ROUTE:
274 if (label & GBL_RTSOCK)
275 rc = 0;
276 break;
277 case PRIV_PROC_LIMIT:
278 case PRIV_PROC_SETRLIMIT:
279 if (label & GBL_PROC)
280 rc = 0;
281 break;
282 case PRIV_SYSCTL_WRITE:
283 if (label & GBL_SYSCTL)
284 rc = 0;
285 break;
286 case PRIV_VFS_READ:
287 case PRIV_VFS_WRITE:
288 if (label & GBL_KMEM)
289 rc = 0;
290 /* FALLTHROUGH */
291 case PRIV_VFS_ADMIN:
292 case PRIV_VFS_BLOCKRESERVE:
293 case PRIV_VFS_CHOWN:
294 case PRIV_VFS_EXEC: /* vaccess file and accmode & VEXEC */
295 case PRIV_VFS_GENERATION:
296 case PRIV_VFS_LOOKUP: /* vaccess DIR */
297 if (label & GBL_VACCESS)
298 rc = 0;
299 break;
300 case PRIV_VERIEXEC_DIRECT:
301 /*
302 * We are here because we are attempting to direct exec
303 * something with the 'indirect' flag set.
304 * We need to check parent label for this one.
305 */
306 PROC_LOCK(curproc);
307 label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label);
308 if (label & GBL_VERIEXEC) {
309 rc = 0;
310 /*
311 * Of course the only reason to be running an
312 * interpreter this way is to bypass O_VERIFY
313 * so we can run unsigned script.
314 * We set GBL_VERIEXEC on p_label for
315 * PRIV_VERIEXEC_NOVERIFY below
316 */
317 SLOT_SET(curproc->p_label, GBL_VERIEXEC);
318 }
319 PROC_UNLOCK(curproc);
320 break;
321 case PRIV_VERIEXEC_NOVERIFY:
322 /* we look at p_label! see above */
323 label = (gbl_label_t)SLOT(curproc->p_label);
324 if (label & GBL_VERIEXEC)
325 rc = 0;
326 break;
327 default:
328 break;
329 }
330 MAC_GRANTBYLABEL_DBG(rc ? 1 : 2,
331 "pid=%d priv=%d, label=%#o rc=%d",
332 curproc->p_pid, priv, label, rc);
333
334 return rc;
335 }
336
337
338 /*
339 * If proc->p_textvp does not yet have a label,
340 * fetch file info from mac_veriexec
341 * and set label (if any) else set.
342 * If there is no label set it to GBL_EMPTY.
343 */
344 static int
mac_grantbylabel_proc_check_resource(struct ucred * cred,struct proc * proc)345 mac_grantbylabel_proc_check_resource(struct ucred *cred,
346 struct proc *proc)
347 {
348 gbl_label_t gbl;
349
350 if (!SLOT(proc->p_textvp->v_label)) {
351 gbl = gbl_get_vlabel(proc->p_textvp, cred);
352 if (gbl == 0)
353 gbl = GBL_EMPTY;
354 SLOT_SET(proc->p_textvp->v_label, gbl);
355 }
356 return 0;
357 }
358
359 static int
mac_grantbylabel_syscall(struct thread * td,int call,void * arg)360 mac_grantbylabel_syscall(struct thread *td, int call, void *arg)
361 {
362 cap_rights_t rights;
363 struct mac_grantbylabel_fetch_gbl_args gbl_args;
364 struct file *fp;
365 struct proc *proc;
366 int error;
367 int proc_locked;
368
369 switch (call) {
370 case MAC_GRANTBYLABEL_FETCH_GBL:
371 case MAC_GRANTBYLABEL_FETCH_PID_GBL:
372 error = copyin(arg, &gbl_args, sizeof(gbl_args));
373 if (error)
374 return error;
375 gbl_args.gbl = 0;
376 break;
377 default:
378 return EOPNOTSUPP;
379 break;
380 }
381 proc_locked = 0;
382 switch (call) {
383 case MAC_GRANTBYLABEL_FETCH_GBL:
384 error = getvnode(td, gbl_args.u.fd,
385 cap_rights_init(&rights), &fp);
386 if (error)
387 return (error);
388
389 if (fp->f_type != DTYPE_VNODE) {
390 error = EINVAL;
391 goto cleanup_file;
392 }
393
394 vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY);
395 gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred);
396 if (gbl_args.gbl == 0)
397 error = EOPNOTSUPP;
398 else
399 error = 0;
400 VOP_UNLOCK(fp->f_vnode);
401 cleanup_file:
402 fdrop(fp, td);
403 break;
404 case MAC_GRANTBYLABEL_FETCH_PID_GBL:
405 error = 0;
406 if (gbl_args.u.pid == 0
407 || gbl_args.u.pid == curproc->p_pid) {
408 proc = curproc;
409 } else {
410 proc = pfind(gbl_args.u.pid);
411 if (proc == NULL)
412 return (EINVAL);
413 proc_locked = 1;
414 }
415 gbl_args.gbl = (SLOT(proc->p_textvp->v_label) |
416 SLOT(proc->p_label));
417 if (proc_locked)
418 PROC_UNLOCK(proc);
419 break;
420 }
421 if (error == 0) {
422 error = copyout(&gbl_args, arg, sizeof(gbl_args));
423 }
424 return error;
425 }
426
427
428 static void
mac_grantbylabel_proc_init_label(struct label * label)429 mac_grantbylabel_proc_init_label(struct label *label)
430 {
431
432 SLOT_SET(label, 0); /* not yet set! */
433 }
434
435 static void
mac_grantbylabel_vnode_init_label(struct label * label)436 mac_grantbylabel_vnode_init_label(struct label *label)
437 {
438
439 SLOT_SET(label, 0); /* not yet set! */
440 }
441
442 /**
443 * @brief set v_label if needed
444 */
445 static int
mac_grantbylabel_vnode_check_exec(struct ucred * cred __unused,struct vnode * vp __unused,struct label * label __unused,struct image_params * imgp,struct label * execlabel __unused)446 mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused,
447 struct vnode *vp __unused, struct label *label __unused,
448 struct image_params *imgp, struct label *execlabel __unused)
449 {
450 gbl_label_t gbl;
451
452 gbl = SLOT(vp->v_label);
453 if (gbl == 0) {
454 gbl = gbl_get_vlabel(vp, cred);
455 if (gbl == 0)
456 gbl = GBL_EMPTY;
457 MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl);
458 SLOT_SET(vp->v_label, gbl);
459 }
460 return 0;
461 }
462
463 static void
mac_grantbylabel_copy_label(struct label * src,struct label * dest)464 mac_grantbylabel_copy_label(struct label *src, struct label *dest)
465 {
466 SLOT_SET(dest, SLOT(src));
467 }
468
469 /**
470 * @brief if interpreting copy script v_label to proc p_label
471 */
472 static int
mac_grantbylabel_vnode_execve_will_transition(struct ucred * old,struct vnode * vp,struct label * vplabel,struct label * interpvplabel,struct image_params * imgp,struct label * execlabel)473 mac_grantbylabel_vnode_execve_will_transition(struct ucred *old,
474 struct vnode *vp, struct label *vplabel,
475 struct label *interpvplabel, struct image_params *imgp,
476 struct label *execlabel)
477 {
478 gbl_label_t gbl;
479
480 if (imgp->interpreted) {
481 gbl = SLOT(interpvplabel);
482 if (gbl) {
483 SLOT_SET(imgp->proc->p_label, gbl);
484 }
485 MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl);
486 }
487 return 0;
488 }
489
490
491 static struct mac_policy_ops mac_grantbylabel_ops =
492 {
493 .mpo_proc_check_resource = mac_grantbylabel_proc_check_resource,
494 .mpo_priv_grant = mac_grantbylabel_priv_grant,
495 .mpo_syscall = mac_grantbylabel_syscall,
496 .mpo_proc_init_label = mac_grantbylabel_proc_init_label,
497 .mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec,
498 .mpo_vnode_copy_label = mac_grantbylabel_copy_label,
499 .mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition,
500 .mpo_vnode_init_label = mac_grantbylabel_vnode_init_label,
501 };
502
503 MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel,
504 MAC_GRANTBYLABEL_FULLNAME,
505 MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot);
506 MODULE_VERSION(mac_grantbylabel, 1);
507 MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION,
508 MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION);
509