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