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