1 /*- 2 * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/param.h> 8 #include <sys/systm.h> 9 #include <sys/ctype.h> 10 #include <sys/jail.h> 11 #include <sys/kernel.h> 12 #include <sys/limits.h> 13 #include <sys/lock.h> 14 #include <sys/malloc.h> 15 #include <sys/module.h> 16 #include <sys/mount.h> 17 #include <sys/mutex.h> 18 #include <sys/priv.h> 19 #include <sys/proc.h> 20 #include <sys/socket.h> 21 #include <sys/sx.h> 22 #include <sys/sysctl.h> 23 #include <sys/ucred.h> 24 #include <sys/vnode.h> 25 26 #include <security/mac/mac_policy.h> 27 28 static SYSCTL_NODE(_security_mac, OID_AUTO, do, 29 CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls"); 30 31 static int do_enabled = 1; 32 SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN, 33 &do_enabled, 0, "Enforce do policy"); 34 35 static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do"); 36 37 #define MAC_RULE_STRING_LEN 1024 38 39 static unsigned mac_do_osd_jail_slot; 40 41 #define RULE_INVALID 0 /* Must stay 0. */ 42 #define RULE_UID 1 43 #define RULE_GID 2 44 #define RULE_ANY 3 45 46 static const char *id_type_to_str[] = { 47 [RULE_INVALID] = "invalid", 48 [RULE_UID] = "uid", 49 [RULE_GID] = "gid", 50 /* See also parse_id_type(). */ 51 [RULE_ANY] = "*", 52 NULL 53 }; 54 55 /* 56 * We assume that 'uid_t' and 'gid_t' are aliases to 'u_int' in conversions 57 * required for parsing rules specification strings. 58 */ 59 _Static_assert(sizeof(uid_t) == sizeof(u_int) && (uid_t)-1 >= 0 && 60 sizeof(gid_t) == sizeof(u_int) && (gid_t)-1 >= 0, 61 "mac_do(4) assumes that 'uid_t' and 'gid_t' are aliases to 'u_int'"); 62 63 struct rule { 64 u_int from_type; 65 u_int from_id; 66 u_int to_type; 67 u_int to_id; 68 TAILQ_ENTRY(rule) r_entries; 69 }; 70 71 struct rules { 72 char string[MAC_RULE_STRING_LEN]; 73 TAILQ_HEAD(rulehead, rule) head; 74 }; 75 76 static void 77 toast_rules(struct rules *const rules) 78 { 79 struct rulehead *const head = &rules->head; 80 struct rule *rule; 81 82 while ((rule = TAILQ_FIRST(head)) != NULL) { 83 TAILQ_REMOVE(head, rule, r_entries); 84 free(rule, M_DO); 85 } 86 free(rules, M_DO); 87 } 88 89 static struct rules * 90 alloc_rules(void) 91 { 92 struct rules *const rules = malloc(sizeof(*rules), M_DO, M_WAITOK); 93 94 _Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!"); 95 rules->string[0] = 0; 96 TAILQ_INIT(&rules->head); 97 return (rules); 98 } 99 100 /* 101 * String to unsigned int. 102 * 103 * Contrary to the "standard" strtou*() family of functions, do not tolerate 104 * spaces at start nor an empty string, and returns a status code, the 'u_int' 105 * result being returned through a passed pointer (if no error). 106 * 107 * We detour through 'quad_t' because in-kernel strto*() functions cannot set 108 * 'errno' and thus can't distinguish a true maximum value from one returned 109 * because of overflow. We use 'quad_t' instead of 'u_quad_t' to support 110 * negative specifications (e.g., such as "-1" for UINT_MAX). 111 */ 112 static int 113 strtoui_strict(const char *const restrict s, const char **const restrict endptr, 114 int base, u_int *result) 115 { 116 char *ep; 117 quad_t q; 118 119 /* Rule out spaces and empty specifications. */ 120 if (s[0] == '\0' || isspace(s[0])) { 121 if (endptr != NULL) 122 *endptr = s; 123 return (EINVAL); 124 } 125 126 q = strtoq(s, &ep, base); 127 if (endptr != NULL) 128 *endptr = ep; 129 if (q < 0) { 130 /* We allow specifying a negative number. */ 131 if (q < -(quad_t)UINT_MAX - 1 || q == QUAD_MIN) 132 return (EOVERFLOW); 133 } else { 134 if (q > UINT_MAX || q == UQUAD_MAX) 135 return (EOVERFLOW); 136 } 137 138 *result = (u_int)q; 139 return (0); 140 } 141 142 static int 143 parse_id_type(const char *const string, int *const type) 144 { 145 /* 146 * Special case for "any", as the canonical form for RULE_ANY in 147 * id_type_to_str[] is "*". 148 */ 149 if (strcmp(string, "any") == 0) { 150 *type = RULE_ANY; 151 return (0); 152 } 153 154 /* Start at 1 to avoid parsing "invalid". */ 155 for (size_t i = 1; id_type_to_str[i] != NULL; ++i) { 156 if (strcmp(string, id_type_to_str[i]) == 0) { 157 *type = i; 158 return (0); 159 } 160 } 161 162 *type = RULE_INVALID; 163 return (EINVAL); 164 } 165 166 static int 167 parse_rule_element(char *element, struct rule **rule) 168 { 169 const char *from_type, *from_id, *to, *p; 170 struct rule *new; 171 int error; 172 173 new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK); 174 175 from_type = strsep(&element, "="); 176 if (from_type == NULL) 177 goto einval; 178 179 error = parse_id_type(from_type, &new->from_type); 180 if (error != 0) 181 goto einval; 182 switch (new->from_type) { 183 case RULE_UID: 184 case RULE_GID: 185 break; 186 default: 187 goto einval; 188 } 189 190 from_id = strsep(&element, ":"); 191 if (from_id == NULL || *from_id == '\0') 192 goto einval; 193 194 error = strtoui_strict(from_id, &p, 10, &new->from_id); 195 if (error != 0 || *p != '\0') 196 goto einval; 197 198 to = element; 199 if (to == NULL || *to == '\0') 200 goto einval; 201 202 if (strcmp(to, "any") == 0 || strcmp(to, "*") == 0) 203 new->to_type = RULE_ANY; 204 else { 205 new->to_type = RULE_UID; 206 error = strtoui_strict(to, &p, 10, &new->to_id); 207 if (error != 0 || *p != '\0') 208 goto einval; 209 } 210 211 *rule = new; 212 return (0); 213 einval: 214 free(new, M_DO); 215 *rule = NULL; 216 return (EINVAL); 217 } 218 219 /* 220 * Parse rules specification and produce rule structures out of it. 221 * 222 * Returns 0 on success, with '*rulesp' made to point to a 'struct rule' 223 * representing the rules. On error, the returned value is non-zero and 224 * '*rulesp' is unchanged. If 'string' has length greater or equal to 225 * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned. If it is not in the expected 226 * format (comma-separated list of clauses of the form "<type>=<val>:<target>", 227 * where <type> is "uid" or "gid", <val> an UID or GID (depending on <type>) and 228 * <target> is "*", "any" or some UID), EINVAL is returned. 229 */ 230 static int 231 parse_rules(const char *const string, struct rules **const rulesp) 232 { 233 const size_t len = strlen(string); 234 char *copy; 235 char *p; 236 char *element; 237 struct rules *rules; 238 struct rule *new; 239 int error = 0; 240 241 if (len >= MAC_RULE_STRING_LEN) 242 return (ENAMETOOLONG); 243 244 rules = alloc_rules(); 245 bcopy(string, rules->string, len + 1); 246 MPASS(rules->string[len] == '\0'); /* Catch some races. */ 247 248 copy = malloc(len + 1, M_DO, M_WAITOK); 249 bcopy(string, copy, len + 1); 250 MPASS(copy[len] == '\0'); /* Catch some races. */ 251 252 p = copy; 253 while ((element = strsep(&p, ",")) != NULL) { 254 if (element[0] == '\0') 255 continue; 256 error = parse_rule_element(element, &new); 257 if (error != 0) { 258 toast_rules(rules); 259 goto out; 260 } 261 TAILQ_INSERT_TAIL(&rules->head, new, r_entries); 262 } 263 264 *rulesp = rules; 265 out: 266 free(copy, M_DO); 267 return (error); 268 } 269 270 /* 271 * Find rules applicable to the passed prison. 272 * 273 * Returns the applicable rules (and never NULL). 'pr' must be unlocked. 274 * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked 275 * once the caller is done accessing the rules. '*aprp' is equal to 'pr' if and 276 * only if the current jail has its own set of rules. 277 */ 278 static struct rules * 279 find_rules(struct prison *const pr, struct prison **const aprp) 280 { 281 struct prison *cpr, *ppr; 282 struct rules *rules; 283 284 cpr = pr; 285 for (;;) { 286 prison_lock(cpr); 287 rules = osd_jail_get(cpr, mac_do_osd_jail_slot); 288 if (rules != NULL) 289 break; 290 prison_unlock(cpr); 291 292 ppr = cpr->pr_parent; 293 MPASS(ppr != NULL); /* prison0 always has rules. */ 294 cpr = ppr; 295 } 296 *aprp = cpr; 297 298 return (rules); 299 } 300 301 /* 302 * OSD destructor for slot 'mac_do_osd_jail_slot'. 303 * 304 * Called with 'value' not NULL. 305 */ 306 static void 307 dealloc_osd(void *const value) 308 { 309 struct rules *const rules = value; 310 311 toast_rules(rules); 312 } 313 314 /* 315 * Remove the rules specifically associated to a prison. 316 * 317 * In practice, this means that the rules become inherited (from the closest 318 * ascendant that has some). 319 * 320 * Destroys the 'mac_do_osd_jail_slot' slot of the passed jail. 321 */ 322 static void 323 remove_rules(struct prison *const pr) 324 { 325 prison_lock(pr); 326 /* This calls destructor dealloc_osd(). */ 327 osd_jail_del(pr, mac_do_osd_jail_slot); 328 prison_unlock(pr); 329 } 330 331 /* 332 * Assign already built rules to a jail. 333 */ 334 static void 335 set_rules(struct prison *const pr, struct rules *const rules) 336 { 337 struct rules *old_rules; 338 void **rsv; 339 340 rsv = osd_reserve(mac_do_osd_jail_slot); 341 342 prison_lock(pr); 343 old_rules = osd_jail_get(pr, mac_do_osd_jail_slot); 344 osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, rules); 345 prison_unlock(pr); 346 if (old_rules != NULL) 347 toast_rules(old_rules); 348 } 349 350 /* 351 * Assigns empty rules to a jail. 352 */ 353 static void 354 set_empty_rules(struct prison *const pr) 355 { 356 struct rules *const rules = alloc_rules(); 357 358 set_rules(pr, rules); 359 } 360 361 /* 362 * Parse a rules specification and assign them to a jail. 363 * 364 * Returns the same error code as parse_rules() (which see). 365 */ 366 static int 367 parse_and_set_rules(struct prison *const pr, const char *rules_string) 368 { 369 struct rules *rules; 370 int error; 371 372 error = parse_rules(rules_string, &rules); 373 if (error != 0) 374 return (error); 375 set_rules(pr, rules); 376 return (0); 377 } 378 379 static int 380 mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS) 381 { 382 char *const buf = malloc(MAC_RULE_STRING_LEN, M_DO, M_WAITOK); 383 struct prison *const td_pr = req->td->td_ucred->cr_prison; 384 struct prison *pr; 385 struct rules *rules; 386 int error; 387 388 rules = find_rules(td_pr, &pr); 389 strlcpy(buf, rules->string, MAC_RULE_STRING_LEN); 390 prison_unlock(pr); 391 392 error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req); 393 if (error != 0 || req->newptr == NULL) 394 goto out; 395 396 /* Set our prison's rules, not that of the jail we inherited from. */ 397 error = parse_and_set_rules(td_pr, buf); 398 out: 399 free(buf, M_DO); 400 return (error); 401 } 402 403 SYSCTL_PROC(_security_mac_do, OID_AUTO, rules, 404 CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_PRISON|CTLFLAG_MPSAFE, 405 0, 0, mac_do_sysctl_rules, "A", 406 "Rules"); 407 408 409 SYSCTL_JAIL_PARAM_SYS_SUBNODE(mac, do, CTLFLAG_RW, "Jail MAC/do parameters"); 410 SYSCTL_JAIL_PARAM_STRING(_mac_do, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN, 411 "Jail MAC/do rules"); 412 413 414 static int 415 mac_do_jail_create(void *obj, void *data __unused) 416 { 417 struct prison *const pr = obj; 418 419 set_empty_rules(pr); 420 return (0); 421 } 422 423 static int 424 mac_do_jail_get(void *obj, void *data) 425 { 426 struct prison *ppr, *const pr = obj; 427 struct vfsoptlist *const opts = data; 428 struct rules *rules; 429 int jsys, error; 430 431 rules = find_rules(pr, &ppr); 432 433 jsys = pr == ppr ? 434 (TAILQ_EMPTY(&rules->head) ? JAIL_SYS_DISABLE : JAIL_SYS_NEW) : 435 JAIL_SYS_INHERIT; 436 error = vfs_setopt(opts, "mac.do", &jsys, sizeof(jsys)); 437 if (error != 0 && error != ENOENT) 438 goto done; 439 440 error = vfs_setopts(opts, "mac.do.rules", rules->string); 441 if (error != 0 && error != ENOENT) 442 goto done; 443 444 error = 0; 445 done: 446 prison_unlock(ppr); 447 return (error); 448 } 449 450 /* 451 * -1 is used as a sentinel in mac_do_jail_check() and mac_do_jail_set() below. 452 */ 453 _Static_assert(-1 != JAIL_SYS_DISABLE && -1 != JAIL_SYS_NEW && 454 -1 != JAIL_SYS_INHERIT, 455 "mac_do(4) uses -1 as a sentinel for uninitialized 'jsys'."); 456 457 /* 458 * We perform only cheap checks here, i.e., we do not really parse the rules 459 * specification string, if any. 460 */ 461 static int 462 mac_do_jail_check(void *obj, void *data) 463 { 464 struct vfsoptlist *opts = data; 465 char *rules_string; 466 int error, jsys, size; 467 468 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 469 if (error == ENOENT) 470 jsys = -1; 471 else { 472 if (error != 0) 473 return (error); 474 if (jsys != JAIL_SYS_DISABLE && jsys != JAIL_SYS_NEW && 475 jsys != JAIL_SYS_INHERIT) 476 return (EINVAL); 477 } 478 479 /* 480 * We use vfs_getopt() here instead of vfs_getopts() to get the length. 481 * We perform the additional checks done by the latter here, even if 482 * jail_set() calls vfs_getopts() itself later (they becoming 483 * inconsistent wouldn't cause any security problem). 484 */ 485 error = vfs_getopt(opts, "mac.do.rules", (void**)&rules_string, &size); 486 if (error == ENOENT) { 487 /* 488 * Default (in absence of "mac.do.rules") is to disable (and, in 489 * particular, not inherit). 490 */ 491 if (jsys == -1) 492 jsys = JAIL_SYS_DISABLE; 493 494 if (jsys == JAIL_SYS_NEW) { 495 vfs_opterror(opts, "'mac.do.rules' must be specified " 496 "given 'mac.do''s value"); 497 return (EINVAL); 498 } 499 500 /* Absence of "mac.do.rules" at this point is OK. */ 501 error = 0; 502 } else { 503 if (error != 0) 504 return (error); 505 506 /* Not a proper string. */ 507 if (size == 0 || rules_string[size - 1] != '\0') { 508 vfs_opterror(opts, "'mac.do.rules' not a proper string"); 509 return (EINVAL); 510 } 511 512 if (size > MAC_RULE_STRING_LEN) { 513 vfs_opterror(opts, "'mdo.rules' too long"); 514 return (ENAMETOOLONG); 515 } 516 517 if (jsys == -1) 518 /* Default (if "mac.do.rules" is present). */ 519 jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE : 520 JAIL_SYS_NEW; 521 522 /* 523 * Be liberal and accept JAIL_SYS_DISABLE and JAIL_SYS_INHERIT 524 * with an explicit empty rules specification. 525 */ 526 switch (jsys) { 527 case JAIL_SYS_DISABLE: 528 case JAIL_SYS_INHERIT: 529 if (rules_string[0] != '\0') { 530 vfs_opterror(opts, "'mac.do.rules' specified " 531 "but should not given 'mac.do''s value"); 532 return (EINVAL); 533 } 534 break; 535 } 536 } 537 538 return (error); 539 } 540 541 static int 542 mac_do_jail_set(void *obj, void *data) 543 { 544 struct prison *pr = obj; 545 struct vfsoptlist *opts = data; 546 char *rules_string; 547 int error, jsys; 548 549 /* 550 * The invariants checks used below correspond to what has already been 551 * checked in jail_check() above. 552 */ 553 554 error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys)); 555 MPASS(error == 0 || error == ENOENT); 556 if (error != 0) 557 jsys = -1; /* Mark unfilled. */ 558 559 rules_string = vfs_getopts(opts, "mac.do.rules", &error); 560 MPASS(error == 0 || error == ENOENT); 561 if (error == 0) { 562 MPASS(strlen(rules_string) < MAC_RULE_STRING_LEN); 563 if (jsys == -1) 564 /* Default (if "mac.do.rules" is present). */ 565 jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE : 566 JAIL_SYS_NEW; 567 else 568 MPASS(jsys == JAIL_SYS_NEW || 569 ((jsys == JAIL_SYS_DISABLE || 570 jsys == JAIL_SYS_INHERIT) && 571 rules_string[0] == '\0')); 572 } else { 573 MPASS(jsys != JAIL_SYS_NEW); 574 if (jsys == -1) 575 /* 576 * Default (in absence of "mac.do.rules") is to disable 577 * (and, in particular, not inherit). 578 */ 579 jsys = JAIL_SYS_DISABLE; 580 /* If disabled, we'll store an empty rule specification. */ 581 if (jsys == JAIL_SYS_DISABLE) 582 rules_string = ""; 583 } 584 585 switch (jsys) { 586 case JAIL_SYS_INHERIT: 587 remove_rules(pr); 588 error = 0; 589 break; 590 case JAIL_SYS_DISABLE: 591 case JAIL_SYS_NEW: 592 error = parse_and_set_rules(pr, rules_string); 593 break; 594 default: 595 __assert_unreachable(); 596 } 597 return (error); 598 } 599 600 /* 601 * OSD jail methods. 602 * 603 * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail 604 * code (see prison_cleanup()), which triggers a run of our dealloc_osd() 605 * destructor. 606 */ 607 static const osd_method_t osd_methods[PR_MAXMETHOD] = { 608 [PR_METHOD_CREATE] = mac_do_jail_create, 609 [PR_METHOD_GET] = mac_do_jail_get, 610 [PR_METHOD_CHECK] = mac_do_jail_check, 611 [PR_METHOD_SET] = mac_do_jail_set, 612 }; 613 614 615 static void 616 mac_do_init(struct mac_policy_conf *mpc) 617 { 618 struct prison *pr; 619 620 mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods); 621 set_empty_rules(&prison0); 622 sx_slock(&allprison_lock); 623 TAILQ_FOREACH(pr, &allprison, pr_list) 624 set_empty_rules(pr); 625 sx_sunlock(&allprison_lock); 626 } 627 628 static void 629 mac_do_destroy(struct mac_policy_conf *mpc) 630 { 631 osd_jail_deregister(mac_do_osd_jail_slot); 632 } 633 634 static bool 635 rule_applies(struct ucred *cred, struct rule *r) 636 { 637 if (r->from_type == RULE_UID && r->from_id == cred->cr_uid) 638 return (true); 639 if (r->from_type == RULE_GID && groupmember(r->from_id, cred)) 640 return (true); 641 return (false); 642 } 643 644 static int 645 mac_do_priv_grant(struct ucred *cred, int priv) 646 { 647 struct rule *r; 648 struct prison *pr; 649 struct rules *rule; 650 651 if (do_enabled == 0) 652 return (EPERM); 653 654 rule = find_rules(cred->cr_prison, &pr); 655 TAILQ_FOREACH(r, &rule->head, r_entries) { 656 if (rule_applies(cred, r)) { 657 switch (priv) { 658 case PRIV_CRED_SETGROUPS: 659 case PRIV_CRED_SETUID: 660 prison_unlock(pr); 661 return (0); 662 default: 663 break; 664 } 665 } 666 } 667 prison_unlock(pr); 668 return (EPERM); 669 } 670 671 static int 672 mac_do_check_setgroups(struct ucred *cred, int ngrp, gid_t *groups) 673 { 674 struct rule *r; 675 char *fullpath = NULL; 676 char *freebuf = NULL; 677 struct prison *pr; 678 struct rules *rule; 679 680 if (do_enabled == 0) 681 return (0); 682 if (cred->cr_uid == 0) 683 return (0); 684 685 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 686 return (EPERM); 687 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 688 free(freebuf, M_TEMP); 689 return (EPERM); 690 } 691 free(freebuf, M_TEMP); 692 693 rule = find_rules(cred->cr_prison, &pr); 694 TAILQ_FOREACH(r, &rule->head, r_entries) { 695 if (rule_applies(cred, r)) { 696 prison_unlock(pr); 697 return (0); 698 } 699 } 700 prison_unlock(pr); 701 702 return (EPERM); 703 } 704 705 static int 706 mac_do_check_setuid(struct ucred *cred, uid_t uid) 707 { 708 struct rule *r; 709 int error; 710 char *fullpath = NULL; 711 char *freebuf = NULL; 712 struct prison *pr; 713 struct rules *rule; 714 715 if (do_enabled == 0) 716 return (0); 717 if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0) 718 return (0); 719 720 if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0) 721 return (EPERM); 722 if (strcmp(fullpath, "/usr/bin/mdo") != 0) { 723 free(freebuf, M_TEMP); 724 return (EPERM); 725 } 726 free(freebuf, M_TEMP); 727 728 error = EPERM; 729 rule = find_rules(cred->cr_prison, &pr); 730 TAILQ_FOREACH(r, &rule->head, r_entries) { 731 if (r->from_type == RULE_UID) { 732 if (cred->cr_uid != r->from_id) 733 continue; 734 if (r->to_type == RULE_ANY) { 735 error = 0; 736 break; 737 } 738 if (r->to_type == RULE_UID && uid == r->to_id) { 739 error = 0; 740 break; 741 } 742 } 743 if (r->from_type == RULE_GID) { 744 if (!groupmember(r->from_id, cred)) 745 continue; 746 if (r->to_type == RULE_ANY) { 747 error = 0; 748 break; 749 } 750 if (r->to_type == RULE_UID && uid == r->to_id) { 751 error = 0; 752 break; 753 } 754 } 755 } 756 prison_unlock(pr); 757 return (error); 758 } 759 760 static struct mac_policy_ops do_ops = { 761 .mpo_destroy = mac_do_destroy, 762 .mpo_init = mac_do_init, 763 .mpo_cred_check_setuid = mac_do_check_setuid, 764 .mpo_cred_check_setgroups = mac_do_check_setgroups, 765 .mpo_priv_grant = mac_do_priv_grant, 766 }; 767 768 MAC_POLICY_SET(&do_ops, mac_do, "MAC/do", 769 MPC_LOADTIME_FLAG_UNLOADOK, NULL); 770 MODULE_VERSION(mac_do, 1); 771