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_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_KMEM_READ: 248 case PRIV_KMEM_WRITE: 249 if (label & GBL_KMEM) 250 rc = 0; 251 break; 252 case PRIV_NETINET_BINDANY: 253 case PRIV_NETINET_RESERVEDPORT: /* socket bind low port */ 254 case PRIV_NETINET_REUSEPORT: 255 if (label & GBL_BIND) 256 rc = 0; 257 break; 258 case PRIV_NETINET_ADDRCTRL6: 259 case PRIV_NET_LAGG: 260 case PRIV_NET_SETIFFIB: 261 case PRIV_NET_SETIFVNET: 262 case PRIV_NETINET_SETHDROPTS: 263 case PRIV_NET_VXLAN: 264 case PRIV_NETINET_GETCRED: 265 case PRIV_NETINET_IPSEC: 266 case PRIV_NETINET_RAW: 267 if (label & GBL_NET) 268 rc = 0; 269 break; 270 case PRIV_NETINET_MROUTE: 271 case PRIV_NET_ROUTE: 272 if (label & GBL_RTSOCK) 273 rc = 0; 274 break; 275 case PRIV_PROC_LIMIT: 276 case PRIV_PROC_SETRLIMIT: 277 if (label & GBL_PROC) 278 rc = 0; 279 break; 280 case PRIV_SYSCTL_WRITE: 281 if (label & GBL_SYSCTL) 282 rc = 0; 283 break; 284 case PRIV_VFS_READ: 285 case PRIV_VFS_WRITE: 286 if (label & GBL_KMEM) 287 rc = 0; 288 /* FALLTHROUGH */ 289 case PRIV_VFS_ADMIN: 290 case PRIV_VFS_BLOCKRESERVE: 291 case PRIV_VFS_CHOWN: 292 case PRIV_VFS_EXEC: /* vaccess file and accmode & VEXEC */ 293 case PRIV_VFS_GENERATION: 294 case PRIV_VFS_LOOKUP: /* vaccess DIR */ 295 if (label & GBL_VACCESS) 296 rc = 0; 297 break; 298 case PRIV_VERIEXEC_DIRECT: 299 /* 300 * We are here because we are attempting to direct exec 301 * something with the 'indirect' flag set. 302 * We need to check parent label for this one. 303 */ 304 PROC_LOCK(curproc); 305 label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label); 306 if (label & GBL_VERIEXEC) { 307 rc = 0; 308 /* 309 * Of course the only reason to be running an 310 * interpreter this way is to bypass O_VERIFY 311 * so we can run unsigned script. 312 * We set GBL_VERIEXEC on p_label for 313 * PRIV_VERIEXEC_NOVERIFY below 314 */ 315 SLOT_SET(curproc->p_label, GBL_VERIEXEC); 316 } 317 PROC_UNLOCK(curproc); 318 break; 319 case PRIV_VERIEXEC_NOVERIFY: 320 /* we look at p_label! see above */ 321 label = (gbl_label_t)SLOT(curproc->p_label); 322 if (label & GBL_VERIEXEC) 323 rc = 0; 324 break; 325 default: 326 break; 327 } 328 MAC_GRANTBYLABEL_DBG(rc ? 1 : 2, 329 "pid=%d priv=%d, label=%#o rc=%d", 330 curproc->p_pid, priv, label, rc); 331 332 return rc; 333 } 334 335 336 /* 337 * If proc->p_textvp does not yet have a label, 338 * fetch file info from mac_veriexec 339 * and set label (if any) else set. 340 * If there is no label set it to GBL_EMPTY. 341 */ 342 static int 343 mac_grantbylabel_proc_check_resource(struct ucred *cred, 344 struct proc *proc) 345 { 346 gbl_label_t gbl; 347 348 if (!SLOT(proc->p_textvp->v_label)) { 349 gbl = gbl_get_vlabel(proc->p_textvp, cred); 350 if (gbl == 0) 351 gbl = GBL_EMPTY; 352 SLOT_SET(proc->p_textvp->v_label, gbl); 353 } 354 return 0; 355 } 356 357 static int 358 mac_grantbylabel_syscall(struct thread *td, int call, void *arg) 359 { 360 cap_rights_t rights; 361 struct mac_grantbylabel_fetch_gbl_args gbl_args; 362 struct file *fp; 363 struct proc *proc; 364 int error; 365 int proc_locked; 366 367 switch (call) { 368 case MAC_GRANTBYLABEL_FETCH_GBL: 369 case MAC_GRANTBYLABEL_FETCH_PID_GBL: 370 error = copyin(arg, &gbl_args, sizeof(gbl_args)); 371 if (error) 372 return error; 373 gbl_args.gbl = 0; 374 break; 375 default: 376 return EOPNOTSUPP; 377 break; 378 } 379 proc_locked = 0; 380 switch (call) { 381 case MAC_GRANTBYLABEL_FETCH_GBL: 382 error = getvnode(td, gbl_args.u.fd, 383 cap_rights_init(&rights), &fp); 384 if (error) 385 return (error); 386 387 if (fp->f_type != DTYPE_VNODE) { 388 error = EINVAL; 389 goto cleanup_file; 390 } 391 392 vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY); 393 gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred); 394 if (gbl_args.gbl == 0) 395 error = EOPNOTSUPP; 396 else 397 error = 0; 398 VOP_UNLOCK(fp->f_vnode); 399 cleanup_file: 400 fdrop(fp, td); 401 break; 402 case MAC_GRANTBYLABEL_FETCH_PID_GBL: 403 error = 0; 404 if (gbl_args.u.pid == 0 405 || gbl_args.u.pid == curproc->p_pid) { 406 proc = curproc; 407 } else { 408 proc = pfind(gbl_args.u.pid); 409 if (proc == NULL) 410 return (EINVAL); 411 proc_locked = 1; 412 } 413 gbl_args.gbl = (SLOT(proc->p_textvp->v_label) | 414 SLOT(proc->p_label)); 415 if (proc_locked) 416 PROC_UNLOCK(proc); 417 break; 418 } 419 if (error == 0) { 420 error = copyout(&gbl_args, arg, sizeof(gbl_args)); 421 } 422 return error; 423 } 424 425 426 static void 427 mac_grantbylabel_proc_init_label(struct label *label) 428 { 429 430 SLOT_SET(label, 0); /* not yet set! */ 431 } 432 433 static void 434 mac_grantbylabel_vnode_init_label(struct label *label) 435 { 436 437 SLOT_SET(label, 0); /* not yet set! */ 438 } 439 440 /** 441 * @brief set v_label if needed 442 */ 443 static int 444 mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused, 445 struct vnode *vp __unused, struct label *label __unused, 446 struct image_params *imgp, struct label *execlabel __unused) 447 { 448 gbl_label_t gbl; 449 450 gbl = SLOT(vp->v_label); 451 if (gbl == 0) { 452 gbl = gbl_get_vlabel(vp, cred); 453 if (gbl == 0) 454 gbl = GBL_EMPTY; 455 MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl); 456 SLOT_SET(vp->v_label, gbl); 457 } 458 return 0; 459 } 460 461 static void 462 mac_grantbylabel_copy_label(struct label *src, struct label *dest) 463 { 464 SLOT_SET(dest, SLOT(src)); 465 } 466 467 /** 468 * @brief if interpreting copy script v_label to proc p_label 469 */ 470 static int 471 mac_grantbylabel_vnode_execve_will_transition(struct ucred *old, 472 struct vnode *vp, struct label *vplabel, 473 struct label *interpvplabel, struct image_params *imgp, 474 struct label *execlabel) 475 { 476 gbl_label_t gbl; 477 478 if (imgp->interpreted) { 479 gbl = SLOT(interpvplabel); 480 if (gbl) { 481 SLOT_SET(imgp->proc->p_label, gbl); 482 } 483 MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl); 484 } 485 return 0; 486 } 487 488 489 static struct mac_policy_ops mac_grantbylabel_ops = 490 { 491 .mpo_proc_check_resource = mac_grantbylabel_proc_check_resource, 492 .mpo_priv_grant = mac_grantbylabel_priv_grant, 493 .mpo_syscall = mac_grantbylabel_syscall, 494 .mpo_proc_init_label = mac_grantbylabel_proc_init_label, 495 .mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec, 496 .mpo_vnode_copy_label = mac_grantbylabel_copy_label, 497 .mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition, 498 .mpo_vnode_init_label = mac_grantbylabel_vnode_init_label, 499 }; 500 501 MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel, 502 MAC_GRANTBYLABEL_FULLNAME, 503 MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot); 504 MODULE_VERSION(mac_grantbylabel, 1); 505 MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION, 506 MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION); 507