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 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 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 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 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 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 428 mac_grantbylabel_proc_init_label(struct label *label) 429 { 430 431 SLOT_SET(label, 0); /* not yet set! */ 432 } 433 434 static void 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 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 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 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