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