xref: /illumos-gate/usr/src/uts/common/os/devpolicy.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Device policy implementation.
31  *
32  * Maintains the device policy table and defines the lookup functions.
33  *
34  * The table contains one entry for each major device number; each
35  * major bucket has a list of minor number specific entries.  First
36  * match gets it.  Not even simple minor names are expanded as that
37  * would cause the device to be loaded.  Non-wildcard entries are expanded
38  * on first match. Wildcard entries are matched each open but the actual
39  * policy is cached with the common snode, so the matching code will
40  * probably be called infrequently.  The trivial wildcard ``*'' does
41  * not cause expensive string expansions and matches.
42  *
43  * When the policy is updated, the the generation count is increased;
44  * whenever a cached policy is used, the generation count is compared;
45  * if there's no match, the device policy is refreshed.
46  *
47  * The special policy "nullpolicy" is used to mean "no checking beyond DAC
48  * needed".  It too will change when the policy is rev'ed to make sure
49  * that devices with nullpolicy are also refreshed.
50  *
51  * The special policy "dfltpolicy" is used for those devices with no
52  * matching policy.  On boot, it is "all privileges required".
53  * This restriction on boot functions as a fail-safe; if no device policy
54  * is loaded a "no restriction policy" would lead to security problems that
55  * are not immediately noticable.
56  */
57 
58 #include <sys/priv_impl.h>
59 #include <sys/policy.h>
60 #include <sys/atomic.h>
61 #include <sys/autoconf.h>
62 #include <sys/sysmacros.h>
63 #include <sys/systm.h>
64 #include <sys/vnode.h>
65 #include <sys/devpolicy.h>
66 #include <sys/priv.h>
67 #include <sys/kmem.h>
68 #include <sys/ksynch.h>
69 #include <sys/errno.h>
70 #include <sys/sunddi.h>
71 #include <c2/audit.h>
72 #include <sys/fs/dv_node.h>
73 
74 /*
75  * Internal data structures definitions.
76  */
77 
78 typedef struct devplcyent devplcyent_t;
79 
80 /*
81  * The device policy entry; if there is an expression string, the
82  * minor numbers are not relevant.  This is indicated by dpe_len > 0.
83  */
84 struct devplcyent {
85 	devplcyent_t	*dpe_next;	/* next entry in this list */
86 	devplcy_t	*dpe_plcy;	/* policy for this entry */
87 	char		*dpe_expr;	/* expression matching minor mode */
88 	int		dpe_len;	/* size of allocated mem for expr */
89 	uint32_t	dpe_flags;	/* flags */
90 	minor_t		dpe_lomin;	/* expanded: low minor number */
91 	minor_t		dpe_himin;	/* expanded: high minor number */
92 	vtype_t		dpe_spec;	/* expanded: VBLK or VCHR */
93 };
94 
95 #define	DPE_WILDC	0x01		/* Expression has wildcard */
96 #define	DPE_ALLMINOR	0x02		/* Matches all minor numbers */
97 #define	DPE_EXPANDED	0x04		/* Minor numbers expanded */
98 
99 typedef struct tableent {
100 	devplcyent_t	*t_ent;		/* list of policies by minor */
101 	major_t		t_major;	/* device major number */
102 } tableent_t;
103 
104 /*
105  * The data store.
106  */
107 
108 static int ntabent;		/* # of major numbers */
109 static int totitems;		/* Number of entries in all buckets + dflt */
110 static tableent_t *devpolicy;	/* The device policy itself */
111 
112 static krwlock_t policyrw;	/* protects the table */
113 static kmutex_t policymutex;	/* allows only one concurrent devpolicy_load */
114 
115 devplcy_t *nullpolicy;		/* public because it's used for shortcuts */
116 static devplcy_t *dfltpolicy;
117 static devplcy_t *netpolicy;
118 
119 /*
120  * Device policy generation count; only device policies matching the
121  * generation count are still valid.
122  */
123 volatile uint32_t devplcy_gen;
124 
125 /*
126  * Tunable: maximum number of device policy entries to load in
127  * a system call.  (Protects KM_SLEEP call)
128  */
129 int maxdevpolicy = MAXDEVPOLICY;
130 
131 /*
132  * Initialize the device policy code
133  */
134 void
135 devpolicy_init(void)
136 {
137 	rw_init(&policyrw, NULL, RW_DRIVER, NULL);
138 	mutex_init(&policymutex, NULL, MUTEX_DRIVER, NULL);
139 
140 	/* The mutex is held here in order to satisfy the ASSERT in dpget() */
141 	mutex_enter(&policymutex);
142 
143 	nullpolicy = dpget();
144 	dfltpolicy = dpget();
145 	netpolicy = dpget();
146 
147 	/*
148 	 * Initially, we refuse access to all devices except
149 	 * to processes with all privileges.
150 	 */
151 	priv_fillset(&dfltpolicy->dp_rdp);
152 	priv_fillset(&dfltpolicy->dp_wrp);
153 
154 	totitems = 1;
155 
156 	devplcy_gen++;
157 	mutex_exit(&policymutex);
158 
159 	/* initialize default network privilege */
160 	priv_emptyset(&netpolicy->dp_rdp);
161 	priv_emptyset(&netpolicy->dp_wrp);
162 	priv_addset(&netpolicy->dp_rdp, PRIV_NET_RAWACCESS);
163 	priv_addset(&netpolicy->dp_wrp, PRIV_NET_RAWACCESS);
164 }
165 
166 /*
167  * Devpolicy reference counting/allocation routines.
168  * cf. crget()/crhold()/crfree().
169  */
170 devplcy_t *
171 dpget(void)
172 {
173 	devplcy_t *dp = kmem_zalloc(sizeof (*dp), KM_SLEEP);
174 
175 	ASSERT(MUTEX_HELD(&policymutex));
176 
177 	dp->dp_ref = 1;
178 	/* New ones belong to the next generation */
179 	dp->dp_gen = devplcy_gen + 1;
180 	return (dp);
181 }
182 
183 void
184 dphold(devplcy_t *dp)
185 {
186 	ASSERT(dp->dp_ref != 0xdeadbeef && dp->dp_ref != 0);
187 	atomic_add_32(&dp->dp_ref, 1);
188 }
189 
190 void
191 dpfree(devplcy_t *dp)
192 {
193 	ASSERT(dp->dp_ref != 0xdeadbeef && dp->dp_ref != 0);
194 	if (atomic_add_32_nv(&dp->dp_ref, -1) == 0)
195 		kmem_free(dp, sizeof (*dp));
196 }
197 
198 /*
199  * Find the policy that matches this device.
200  */
201 static devplcy_t *
202 match_policy(devplcyent_t *de, dev_t dev, vtype_t spec)
203 {
204 	char *mname = NULL;
205 	minor_t min = getminor(dev);
206 
207 	for (; de != NULL; de = de->dpe_next) {
208 		if (de->dpe_flags & DPE_ALLMINOR)
209 			break;
210 
211 		if (de->dpe_flags & DPE_EXPANDED) {
212 			if (min >= de->dpe_lomin && min <= de->dpe_himin &&
213 			    spec == de->dpe_spec) {
214 				break;
215 			} else {
216 				continue;
217 			}
218 		}
219 
220 		/*
221 		 * We now need the minor name to match string or
222 		 * simle regexp.  Could we use csp->s_dip and not
223 		 * allocate a string here?
224 		 */
225 		if (mname == NULL &&
226 		    ddi_lyr_get_minor_name(dev, spec, &mname) != DDI_SUCCESS)
227 			/* mname can be set after the function fails */
228 			return (dfltpolicy);
229 
230 		/* Simple wildcard, with only one ``*'' */
231 		if (de->dpe_flags & DPE_WILDC) {
232 			int plen = de->dpe_len - 1;
233 			int slen = strlen(mname);
234 			char *pp = de->dpe_expr;
235 			char *sp = mname;
236 
237 			/* string must be at least as long as pattern w/o '*' */
238 			if (slen < plen - 1)
239 				continue;
240 
241 			/* skip prefix */
242 			while (*pp == *sp && *pp != '\0') {
243 				pp++;
244 				sp++;
245 			}
246 			/* matched single '*' */
247 			if (*pp == '\0')
248 				if (*sp == '\0')
249 					break;
250 				else
251 					continue;
252 			if (*pp != '*')
253 				continue;
254 
255 			pp++;
256 			/*
257 			 * skip characters matched by '*': difference of
258 			 * length of s and length of pattern sans '*'
259 			 */
260 			sp += slen - (plen - 1);
261 			if (strcmp(pp, sp) == 0) 	/* match! */
262 				break;
263 
264 		} else if (strcmp(de->dpe_expr, mname) == 0) {
265 			/* Store minor number, if no contention */
266 			if (rw_tryupgrade(&policyrw)) {
267 				de->dpe_lomin = de->dpe_himin = min;
268 				de->dpe_spec = spec;
269 				de->dpe_flags |= DPE_EXPANDED;
270 			}
271 			break;
272 		}
273 
274 	}
275 
276 	if (mname != NULL)
277 		kmem_free(mname, strlen(mname) + 1);
278 
279 	return (de != NULL ? de->dpe_plcy : dfltpolicy);
280 }
281 
282 static int
283 devpolicyent_bymajor(major_t maj)
284 {
285 	int lo, hi;
286 
287 	ASSERT(RW_LOCK_HELD(&policyrw));
288 
289 	lo = 0;
290 	hi = ntabent - 1;
291 
292 	/* Binary search for major number */
293 	while (lo <= hi) {
294 		int mid = (lo + hi) / 2;
295 
296 		if (devpolicy[mid].t_major == maj)
297 			return (mid);
298 		else if (maj < devpolicy[mid].t_major)
299 			hi = mid - 1;
300 		else
301 			lo = mid + 1;
302 	}
303 	return (-1);
304 }
305 
306 /*
307  * Returns held device policy for the specific device node.
308  * Note devfs_devpolicy returns with a hold on the policy.
309  */
310 devplcy_t *
311 devpolicy_find(vnode_t *vp)
312 {
313 	dev_t dev = vp->v_rdev;
314 	vtype_t spec = vp->v_type;
315 	major_t maj = getmajor(dev);
316 	int i;
317 	devplcy_t *res;
318 
319 	if (maj == clone_major)
320 		maj = getminor(dev);
321 
322 	rw_enter(&policyrw, RW_READER);
323 
324 	i = devpolicyent_bymajor(maj);
325 
326 	if (i != -1) {
327 		res = match_policy(devpolicy[i].t_ent, dev, spec);
328 		dphold(res);
329 	} else if (devfs_devpolicy(vp, &res) != 0) {
330 		res = NETWORK_DRV(maj) ? netpolicy : dfltpolicy;
331 		dphold(res);
332 	}
333 
334 	rw_exit(&policyrw);
335 
336 	return (res);
337 }
338 
339 static devplcyent_t *
340 parse_policy(devplcysys_t *ds, devplcy_t *nullp, devplcy_t *defp)
341 {
342 	devplcyent_t *de = kmem_zalloc(sizeof (*de), KM_SLEEP);
343 	devplcy_t *np;
344 
345 	if (priv_isemptyset(&ds->dps_rdp) && priv_isemptyset(&ds->dps_wrp))
346 		dphold(np = nullp);
347 	else if (defp != nullp &&
348 		    priv_isequalset(&ds->dps_rdp, &defp->dp_rdp) &&
349 		    priv_isequalset(&ds->dps_wrp, &defp->dp_wrp))
350 		dphold(np = defp);
351 	else {
352 		np = dpget();
353 		np->dp_rdp = ds->dps_rdp;
354 		np->dp_wrp = ds->dps_wrp;
355 	}
356 
357 	if (ds->dps_minornm[0] != '\0') {
358 		de->dpe_len = strlen(ds->dps_minornm) + 1;
359 
360 		if (strchr(ds->dps_minornm, '*') != NULL) {
361 			if (de->dpe_len == 2) {		/* "*\0" */
362 				de->dpe_flags = DPE_ALLMINOR;
363 				de->dpe_len = 0;
364 			} else
365 				de->dpe_flags = DPE_WILDC;
366 		}
367 		if (de->dpe_len != 0) {
368 			de->dpe_expr = kmem_alloc(de->dpe_len, KM_SLEEP);
369 			(void) strcpy(de->dpe_expr, ds->dps_minornm);
370 		}
371 	} else {
372 		de->dpe_lomin = ds->dps_lomin;
373 		de->dpe_himin = ds->dps_himin;
374 		de->dpe_flags = DPE_EXPANDED;
375 		de->dpe_spec = ds->dps_isblock ? VBLK : VCHR;
376 	}
377 	de->dpe_plcy = np;
378 
379 	ASSERT((de->dpe_flags & (DPE_ALLMINOR|DPE_EXPANDED)) ||
380 		de->dpe_expr != NULL);
381 
382 	return (de);
383 }
384 
385 static void
386 freechain(devplcyent_t *de)
387 {
388 	devplcyent_t *dn;
389 
390 	do {
391 		dn = de->dpe_next;
392 		dpfree(de->dpe_plcy);
393 		if (de->dpe_len != 0)
394 			kmem_free(de->dpe_expr, de->dpe_len);
395 		kmem_free(de, sizeof (*de));
396 		de = dn;
397 	} while (de != NULL);
398 }
399 
400 /*
401  * Load the device policy.
402  * The device policy currently makes nu distinction between the
403  * block and characters devices; that is generally not a problem
404  * as the names of those devices cannot clash.
405  */
406 int
407 devpolicy_load(int nitems, size_t sz, devplcysys_t *uitmp)
408 {
409 	int i, j;
410 	int nmaj = 0;
411 	major_t lastmajor;
412 	devplcysys_t *items;
413 	size_t mem;
414 	major_t curmaj;
415 	devplcyent_t **last, *de;
416 
417 	tableent_t *newpolicy, *oldpolicy;
418 	devplcy_t *newnull, *newdflt, *oldnull, *olddflt;
419 	int oldcnt;
420 	int lastlen;
421 	int lastwild;
422 
423 #ifdef lint
424 	/* Lint can't figure out that the "i == 1" test protects all */
425 	lastlen = 0;
426 	lastwild = 0;
427 	lastmajor = 0;
428 #endif
429 	/*
430 	 * The application must agree with the kernel on the size of each
431 	 * item; it must not exceed the maximum number and must be
432 	 * at least 1 item in size.
433 	 */
434 	if (sz != sizeof (devplcysys_t) || nitems > maxdevpolicy || nitems < 1)
435 		return (EINVAL);
436 
437 	mem = nitems * sz;
438 
439 	items = kmem_alloc(mem, KM_SLEEP);
440 
441 	if (copyin(uitmp, items, mem)) {
442 		kmem_free(items, mem);
443 		return (EFAULT);
444 	}
445 
446 	/* Check for default policy, it must exist and be sorted first */
447 	if (items[0].dps_maj != DEVPOLICY_DFLT_MAJ) {
448 		kmem_free(items, mem);
449 		return (EINVAL);
450 	}
451 
452 	/*
453 	 * Application must deliver entries sorted.
454 	 * Sorted meaning here:
455 	 *	In major number order
456 	 *	For each major number, we first need to have the explicit
457 	 *	entries, then the wild card entries, longest first.
458 	 */
459 	for (i = 1; i < nitems; i++) {
460 		int len, wild;
461 		char *tmp;
462 
463 		curmaj = items[i].dps_maj;
464 		len = strlen(items[i].dps_minornm);
465 		wild = len > 0 &&
466 			(tmp = strchr(items[i].dps_minornm, '*')) != NULL;
467 
468 		/* Another default major, string too long or too many ``*'' */
469 		if (curmaj == DEVPOLICY_DFLT_MAJ ||
470 		    len >= sizeof (items[i].dps_minornm) ||
471 		    wild && strchr(tmp + 1, '*') != NULL) {
472 			kmem_free(items, mem);
473 			return (EINVAL);
474 		}
475 		if (i == 1 || lastmajor < curmaj) {
476 			lastmajor = curmaj;
477 			nmaj++;
478 		} else if (lastmajor > curmaj || lastwild > wild ||
479 				lastwild && lastlen < len) {
480 			kmem_free(items, mem);
481 			return (EINVAL);
482 		}
483 		lastlen = len;
484 		lastwild = wild;
485 	}
486 
487 #ifdef C2_AUDIT
488 	if (audit_active)
489 		audit_devpolicy(nitems, items);
490 #endif
491 
492 	/*
493 	 * Parse the policy.  We create an array for all major numbers
494 	 * and in each major number bucket we'll have a linked list of
495 	 * entries.  Each item may contain either a lo,hi minor pair
496 	 * or a string/wild card matching a minor node.
497 	 */
498 	if (nmaj > 0)
499 		newpolicy = kmem_zalloc(nmaj * sizeof (tableent_t), KM_SLEEP);
500 
501 	/*
502 	 * We want to lock out concurrent updates but we don't want to
503 	 * lock out device opens while we still need to allocate memory.
504 	 * As soon as we allocate new devplcy_t's we commit to the next
505 	 * generation number, so we must lock out other updates from here.
506 	 */
507 	mutex_enter(&policymutex);
508 
509 	/* New default and NULL policy */
510 	newnull = dpget();
511 
512 	if (priv_isemptyset(&items[0].dps_rdp) &&
513 	    priv_isemptyset(&items[0].dps_wrp)) {
514 		newdflt = newnull;
515 		dphold(newdflt);
516 	} else {
517 		newdflt = dpget();
518 		newdflt->dp_rdp = items[0].dps_rdp;
519 		newdflt->dp_wrp = items[0].dps_wrp;
520 	}
521 
522 	j = -1;
523 
524 	/* Userland made sure sorting was ok */
525 	for (i = 1; i < nitems; i++) {
526 		de = parse_policy(&items[i], newnull, newdflt);
527 
528 		if (j == -1 || curmaj != items[i].dps_maj) {
529 			j++;
530 			newpolicy[j].t_major = curmaj = items[i].dps_maj;
531 			last = &newpolicy[j].t_ent;
532 		}
533 		*last = de;
534 		last = &de->dpe_next;
535 	}
536 
537 	/* Done parsing, throw away input */
538 	kmem_free(items, mem);
539 
540 	/* Lock out all devpolicy_find()s */
541 	rw_enter(&policyrw, RW_WRITER);
542 
543 	/* Install the new global data */
544 	oldnull = nullpolicy;
545 	nullpolicy = newnull;
546 
547 	olddflt = dfltpolicy;
548 	dfltpolicy = newdflt;
549 
550 	oldcnt = ntabent;
551 	ntabent = nmaj;
552 
553 	totitems = nitems;
554 
555 	oldpolicy = devpolicy;
556 	devpolicy = newpolicy;
557 
558 	/* Force all calls by devpolicy_find() */
559 	devplcy_gen++;
560 
561 	/* Reenable policy finds */
562 	rw_exit(&policyrw);
563 	mutex_exit(&policymutex);
564 
565 	/* Free old stuff */
566 	if (oldcnt != 0) {
567 		for (i = 0; i < oldcnt; i++)
568 			freechain(oldpolicy[i].t_ent);
569 		kmem_free(oldpolicy, oldcnt * sizeof (*oldpolicy));
570 	}
571 
572 	dpfree(oldnull);
573 	dpfree(olddflt);
574 
575 	return (0);
576 }
577 
578 /*
579  * Get device policy: argument one is a pointer to an integer holding
580  * the number of items allocated for the 3rd argument; the size argument
581  * is a revision check between kernel and userland.
582  */
583 int
584 devpolicy_get(int *nitemp, size_t sz, devplcysys_t *uitmp)
585 {
586 	int i;
587 	devplcyent_t *de;
588 	devplcysys_t *itmp;
589 	int ind;
590 	int nitems;
591 	int err = 0;
592 	size_t alloced;
593 
594 	if (sz != sizeof (devplcysys_t))
595 		return (EINVAL);
596 
597 	if (copyin(nitemp, &nitems, sizeof (nitems)))
598 		return (EFAULT);
599 
600 	rw_enter(&policyrw, RW_READER);
601 
602 	if (copyout(&totitems, nitemp, sizeof (totitems)))
603 		err = EFAULT;
604 	else if (nitems < totitems)
605 		err = ENOMEM;
606 
607 	if (err != 0) {
608 		rw_exit(&policyrw);
609 		return (err);
610 	}
611 
612 	alloced = totitems * sizeof (devplcysys_t);
613 	itmp = kmem_zalloc(alloced, KM_SLEEP);
614 
615 	itmp[0].dps_rdp = dfltpolicy->dp_rdp;
616 	itmp[0].dps_wrp = dfltpolicy->dp_wrp;
617 	itmp[0].dps_maj = DEVPOLICY_DFLT_MAJ;
618 
619 	ind = 1;
620 
621 	for (i = 0; i < ntabent; i++) {
622 		for (de = devpolicy[i].t_ent; de != NULL; de = de->dpe_next) {
623 			itmp[ind].dps_maj = devpolicy[i].t_major;
624 			itmp[ind].dps_rdp = de->dpe_plcy->dp_rdp;
625 			itmp[ind].dps_wrp = de->dpe_plcy->dp_wrp;
626 			if (de->dpe_len)
627 				(void) strcpy(itmp[ind].dps_minornm,
628 						de->dpe_expr);
629 			else if (de->dpe_flags & DPE_ALLMINOR)
630 				(void) strcpy(itmp[ind].dps_minornm, "*");
631 			else {
632 				itmp[ind].dps_lomin = de->dpe_lomin;
633 				itmp[ind].dps_himin = de->dpe_himin;
634 				itmp[ind].dps_isblock = de->dpe_spec == VBLK;
635 			}
636 			ind++;
637 		}
638 	}
639 
640 	rw_exit(&policyrw);
641 
642 	if (copyout(itmp, uitmp, alloced))
643 		err = EFAULT;
644 
645 	kmem_free(itmp, alloced);
646 	return (err);
647 }
648 
649 /*
650  * Get device policy by device name.
651  * This is the implementation of MODGETDEVPOLICYBYNAME
652  */
653 int
654 devpolicy_getbyname(size_t sz, devplcysys_t *uitmp, char *devname)
655 {
656 	devplcysys_t itm;
657 	devplcy_t *plcy;
658 	vtype_t spec;
659 	vnode_t *vp;
660 
661 	if (sz != sizeof (devplcysys_t))
662 		return (EINVAL);
663 
664 	if (lookupname(devname, UIO_USERSPACE, FOLLOW,
665 	    NULLVPP, &vp) != 0)
666 		return (EINVAL);
667 
668 	spec = vp->v_type;
669 	if (spec != VBLK && spec != VCHR) {
670 		VN_RELE(vp);
671 		return (EINVAL);
672 	}
673 
674 	plcy = devpolicy_find(vp);
675 	VN_RELE(vp);
676 
677 	bzero(&itm, sizeof (itm));
678 
679 	/* These are the only values of interest */
680 	itm.dps_rdp = plcy->dp_rdp;
681 	itm.dps_wrp = plcy->dp_wrp;
682 
683 	dpfree(plcy);
684 
685 	if (copyout(&itm, uitmp, sz))
686 		return (EFAULT);
687 	else
688 		return (0);
689 }
690 
691 static void
692 priv_str_to_set(const char *priv_name, priv_set_t *priv_set)
693 {
694 	if (priv_name == NULL || strcmp(priv_name, "none") == 0) {
695 		priv_emptyset(priv_set);
696 	} else if (strcmp(priv_name, "all") == 0) {
697 		priv_fillset(priv_set);
698 	} else {
699 		int priv;
700 		priv = priv_getbyname(priv_name, PRIV_ALLOC);
701 		if (priv < 0) {
702 			cmn_err(CE_WARN, "fail to allocate privilege: %s",
703 			    priv_name);
704 			return;
705 		}
706 		priv_emptyset(priv_set);
707 		priv_addset(priv_set, priv);
708 	}
709 }
710 
711 /*
712  * Return device privileges by privilege name
713  * Called by ddi_create_priv_minor_node()
714  */
715 devplcy_t *
716 devpolicy_priv_by_name(const char *read_priv, const char *write_priv)
717 {
718 	devplcy_t *dp;
719 	mutex_enter(&policymutex);
720 	dp = dpget();
721 	mutex_exit(&policymutex);
722 	priv_str_to_set(read_priv, &dp->dp_rdp);
723 	priv_str_to_set(write_priv, &dp->dp_wrp);
724 
725 	return (dp);
726 }
727