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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2015, Joyent Inc.
24  * Copyright (c) 2017, Joyent, Inc.
25  * Copyright 2025 MNX Cloud, Inc.
26  * Copyright 2025 Oxide Computer Company
27  */
28 
29 /*
30  * Data-Link Driver
31  */
32 
33 #include	<sys/conf.h>
34 #include	<sys/mkdev.h>
35 #include	<sys/modctl.h>
36 #include	<sys/stat.h>
37 #include	<sys/dld_impl.h>
38 #include	<sys/dld_ioc.h>
39 #include	<sys/dls_impl.h>
40 #include	<sys/softmac.h>
41 #include	<sys/mac.h>
42 #include	<sys/mac_ether.h>
43 #include	<sys/mac_client.h>
44 #include	<sys/mac_client_impl.h>
45 #include	<sys/mac_client_priv.h>
46 #include	<inet/common.h>
47 #include	<sys/policy.h>
48 #include	<sys/priv_names.h>
49 #include	<sys/zone.h>
50 #include	<sys/sysmacros.h>
51 
52 static void	drv_init(void);
53 static int	drv_fini(void);
54 
55 static int	drv_getinfo(dev_info_t	*, ddi_info_cmd_t, void *, void **);
56 static int	drv_attach(dev_info_t *, ddi_attach_cmd_t);
57 static int	drv_detach(dev_info_t *, ddi_detach_cmd_t);
58 
59 /*
60  * Secure objects declarations
61  */
62 #define	SECOBJ_WEP_HASHSZ	67
63 static krwlock_t	drv_secobj_lock;
64 static kmem_cache_t	*drv_secobj_cachep;
65 static mod_hash_t	*drv_secobj_hash;
66 static void		drv_secobj_init(void);
67 static void		drv_secobj_fini(void);
68 static int		drv_ioc_setap(datalink_id_t, struct dlautopush *);
69 static int		drv_ioc_getap(datalink_id_t, struct dlautopush *);
70 static int		drv_ioc_clrap(datalink_id_t);
71 
72 
73 /*
74  * The following entry points are private to dld and are used for control
75  * operations only. The entry points exported to mac drivers are defined
76  * in dld_str.c. Refer to the comment on top of dld_str.c for details.
77  */
78 static int	drv_open(dev_t *, int, int, cred_t *);
79 static int	drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
80 
81 static dev_info_t	*dld_dip;	/* dev_info_t for the driver */
82 uint32_t		dld_opt = 0;	/* Global options */
83 
84 #define	NAUTOPUSH 32
85 static mod_hash_t *dld_ap_hashp;
86 static krwlock_t dld_ap_hash_lock;
87 
88 static struct cb_ops drv_cb_ops = {
89 	drv_open,		/* open */
90 	nulldev,		/* close */
91 	nulldev,		/* strategy */
92 	nulldev,		/* print */
93 	nodev,			/* dump */
94 	nodev,			/* read */
95 	nodev,			/* write */
96 	drv_ioctl,		/* ioctl */
97 	nodev,			/* devmap */
98 	nodev,			/* mmap */
99 	nodev,			/* segmap */
100 	nochpoll,		/* poll */
101 	ddi_prop_op,		/* cb_prop_op */
102 	0,			/* streamtab  */
103 	D_MP			/* Driver compatibility flag */
104 };
105 
106 static struct dev_ops drv_ops = {
107 	DEVO_REV,		/* devo_rev */
108 	0,			/* refcnt */
109 	drv_getinfo,		/* get_dev_info */
110 	nulldev,		/* identify */
111 	nulldev,		/* probe */
112 	drv_attach,		/* attach */
113 	drv_detach,		/* detach */
114 	nodev,			/* reset */
115 	&drv_cb_ops,		/* driver operations */
116 	NULL,			/* bus operations */
117 	nodev,			/* dev power */
118 	ddi_quiesce_not_supported,	/* dev quiesce */
119 };
120 
121 /*
122  * Module linkage information for the kernel.
123  */
124 static	struct modldrv		drv_modldrv = {
125 	&mod_driverops,
126 	DLD_INFO,
127 	&drv_ops
128 };
129 
130 static	struct modlinkage	drv_modlinkage = {
131 	MODREV_1,
132 	&drv_modldrv,
133 	NULL
134 };
135 
136 int
_init(void)137 _init(void)
138 {
139 	return (mod_install(&drv_modlinkage));
140 }
141 
142 int
_fini(void)143 _fini(void)
144 {
145 	return (mod_remove(&drv_modlinkage));
146 }
147 
148 int
_info(struct modinfo * modinfop)149 _info(struct modinfo *modinfop)
150 {
151 	return (mod_info(&drv_modlinkage, modinfop));
152 }
153 
154 /*
155  * Initialize component modules.
156  */
157 static void
drv_init(void)158 drv_init(void)
159 {
160 	drv_secobj_init();
161 	dld_str_init();
162 
163 	/*
164 	 * Create a hash table for autopush configuration.
165 	 */
166 	dld_ap_hashp = mod_hash_create_idhash("dld_autopush_hash",
167 	    NAUTOPUSH, mod_hash_null_valdtor);
168 
169 	ASSERT(dld_ap_hashp != NULL);
170 	rw_init(&dld_ap_hash_lock, NULL, RW_DRIVER, NULL);
171 }
172 
173 /* ARGSUSED */
174 static uint_t
drv_ap_exist(mod_hash_key_t key,mod_hash_val_t * val,void * arg)175 drv_ap_exist(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
176 {
177 	boolean_t *pexist = arg;
178 
179 	*pexist = B_TRUE;
180 	return (MH_WALK_TERMINATE);
181 }
182 
183 static int
drv_fini(void)184 drv_fini(void)
185 {
186 	int		err;
187 	boolean_t	exist = B_FALSE;
188 
189 	rw_enter(&dld_ap_hash_lock, RW_READER);
190 	mod_hash_walk(dld_ap_hashp, drv_ap_exist, &exist);
191 	rw_exit(&dld_ap_hash_lock);
192 	if (exist)
193 		return (EBUSY);
194 
195 	if ((err = dld_str_fini()) != 0)
196 		return (err);
197 
198 	drv_secobj_fini();
199 	mod_hash_destroy_idhash(dld_ap_hashp);
200 	rw_destroy(&dld_ap_hash_lock);
201 	return (0);
202 }
203 
204 /*
205  * devo_getinfo: getinfo(9e)
206  */
207 /*ARGSUSED*/
208 static int
drv_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resp)209 drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
210 {
211 	if (dld_dip == NULL)
212 		return (DDI_FAILURE);
213 
214 	switch (cmd) {
215 	case DDI_INFO_DEVT2INSTANCE:
216 		*resp = 0;
217 		break;
218 	case DDI_INFO_DEVT2DEVINFO:
219 		*resp = dld_dip;
220 		break;
221 	default:
222 		return (DDI_FAILURE);
223 	}
224 
225 	return (DDI_SUCCESS);
226 }
227 
228 /*
229  * Check properties to set options. (See dld.h for property definitions).
230  */
231 static void
drv_set_opt(dev_info_t * dip)232 drv_set_opt(dev_info_t *dip)
233 {
234 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
235 	    DLD_PROP_NO_FASTPATH, 0) != 0) {
236 		dld_opt |= DLD_OPT_NO_FASTPATH;
237 	}
238 
239 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
240 	    DLD_PROP_NO_POLL, 0) != 0) {
241 		dld_opt |= DLD_OPT_NO_POLL;
242 	}
243 
244 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
245 	    DLD_PROP_NO_ZEROCOPY, 0) != 0) {
246 		dld_opt |= DLD_OPT_NO_ZEROCOPY;
247 	}
248 
249 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
250 	    DLD_PROP_NO_SOFTRING, 0) != 0) {
251 		dld_opt |= DLD_OPT_NO_SOFTRING;
252 	}
253 }
254 
255 /*
256  * devo_attach: attach(9e)
257  */
258 static int
drv_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)259 drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
260 {
261 	if (cmd != DDI_ATTACH)
262 		return (DDI_FAILURE);
263 
264 	ASSERT(ddi_get_instance(dip) == 0);
265 	drv_init();
266 	drv_set_opt(dip);
267 
268 	/*
269 	 * Create control node. DLPI provider nodes will be created on demand.
270 	 */
271 	if (ddi_create_minor_node(dip, DLD_CONTROL_MINOR_NAME, S_IFCHR,
272 	    DLD_CONTROL_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS)
273 		return (DDI_FAILURE);
274 
275 	dld_dip = dip;
276 
277 	/*
278 	 * Log the fact that the driver is now attached.
279 	 */
280 	ddi_report_dev(dip);
281 	return (DDI_SUCCESS);
282 }
283 
284 /*
285  * devo_detach: detach(9e)
286  */
287 static int
drv_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)288 drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
289 {
290 	if (cmd != DDI_DETACH)
291 		return (DDI_FAILURE);
292 
293 	ASSERT(dld_dip == dip);
294 	if (drv_fini() != 0)
295 		return (DDI_FAILURE);
296 
297 	/*
298 	 * Remove the control node.
299 	 */
300 	ddi_remove_minor_node(dip, DLD_CONTROL_MINOR_NAME);
301 	dld_dip = NULL;
302 
303 	return (DDI_SUCCESS);
304 }
305 
306 /*
307  * dld control node open procedure.
308  */
309 /*ARGSUSED*/
310 static int
drv_open(dev_t * devp,int flag,int sflag,cred_t * credp)311 drv_open(dev_t *devp, int flag, int sflag, cred_t *credp)
312 {
313 	/*
314 	 * Only the control node can be opened.
315 	 */
316 	if (getminor(*devp) != DLD_CONTROL_MINOR)
317 		return (ENODEV);
318 	return (0);
319 }
320 
321 /*
322  * Verify if the caller is allowed to modify a link of the given class.
323  */
324 static int
drv_ioc_checkprivs(datalink_class_t class,cred_t * cred)325 drv_ioc_checkprivs(datalink_class_t class, cred_t *cred)
326 {
327 	if (class == DATALINK_CLASS_IPTUN)
328 		return (secpolicy_iptun_config(cred));
329 	return (secpolicy_dl_config(cred));
330 }
331 
332 /*
333  * DLDIOC_ATTR
334  */
335 /* ARGSUSED */
336 static int
drv_ioc_attr(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)337 drv_ioc_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
338 {
339 	dld_ioc_attr_t		*diap = karg;
340 	dls_dl_handle_t		dlh;
341 	dls_link_t		*dlp;
342 	zoneid_t		zoneid = crgetzoneid(cred);
343 	int			err;
344 	mac_perim_handle_t	mph;
345 
346 	if (zoneid != GLOBAL_ZONEID &&
347 	    zone_check_datalink(&zoneid, diap->dia_linkid) != 0)
348 		return (ENOENT);
349 
350 	if ((err = dls_devnet_hold_tmp(diap->dia_linkid, &dlh)) != 0)
351 		return (err);
352 
353 	if ((err = mac_perim_enter_by_macname(dls_devnet_mac(dlh),
354 	    &mph)) != 0) {
355 		dls_devnet_rele_tmp(dlh);
356 		return (err);
357 	}
358 
359 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0) {
360 		mac_perim_exit(mph);
361 		dls_devnet_rele_tmp(dlh);
362 		return (err);
363 	}
364 
365 	mac_sdu_get(dlp->dl_mh, NULL, &diap->dia_max_sdu);
366 	dls_link_rele(dlp);
367 	mac_perim_exit(mph);
368 	dls_devnet_rele_tmp(dlh);
369 
370 	return (0);
371 }
372 
373 /*
374  * DLDIOC_PHYS_ATTR
375  */
376 /* ARGSUSED */
377 static int
drv_ioc_phys_attr(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)378 drv_ioc_phys_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
379 {
380 	dld_ioc_phys_attr_t	*dipp = karg;
381 	int			err;
382 	dls_dl_handle_t		dlh;
383 	dls_dev_handle_t	ddh;
384 	dev_t			phydev;
385 	zoneid_t		zoneid = crgetzoneid(cred);
386 
387 	if (zoneid != GLOBAL_ZONEID &&
388 	    zone_check_datalink(&zoneid, dipp->dip_linkid) != 0)
389 		return (ENOENT);
390 
391 	/*
392 	 * Every physical link should have its physical dev_t kept in the
393 	 * daemon. If not, it is not a valid physical link.
394 	 */
395 	if (dls_mgmt_get_phydev(dipp->dip_linkid, &phydev) != 0)
396 		return (EINVAL);
397 
398 	/*
399 	 * Although this is a valid physical link, it might already be removed
400 	 * by DR or during system shutdown. softmac_hold_device() would return
401 	 * ENOENT in this case.
402 	 */
403 	if ((err = softmac_hold_device(phydev, &ddh)) != 0)
404 		return (err);
405 
406 	if (dls_devnet_hold_tmp(dipp->dip_linkid, &dlh) != 0) {
407 		/*
408 		 * Although this is an active physical link, its link type is
409 		 * not supported by GLDv3, and therefore it does not have
410 		 * vanity naming support.
411 		 */
412 		dipp->dip_novanity = B_TRUE;
413 	} else {
414 		dipp->dip_novanity = B_FALSE;
415 		dls_devnet_rele_tmp(dlh);
416 	}
417 	/*
418 	 * Get the physical device name from the major number and the instance
419 	 * number derived from phydev.
420 	 */
421 	(void) snprintf(dipp->dip_dev, MAXLINKNAMELEN, "%s%d",
422 	    ddi_major_to_name(getmajor(phydev)), getminor(phydev) - 1);
423 
424 	softmac_rele_device(ddh);
425 	return (0);
426 }
427 
428 /* ARGSUSED */
429 static int
drv_ioc_hwgrpget(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)430 drv_ioc_hwgrpget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
431 {
432 	dld_ioc_hwgrpget_t	*hwgrpp = karg;
433 	dld_hwgrpinfo_t		hwgrp, *hip;
434 	mac_handle_t		mh = NULL;
435 	int			i, err, rgrpnum, tgrpnum;
436 	uint_t			bytes_left;
437 	int			totgrps = 0;
438 	zoneid_t		zoneid = crgetzoneid(cred);
439 
440 	if (zoneid != GLOBAL_ZONEID &&
441 	    zone_check_datalink(&zoneid, hwgrpp->dih_linkid) != 0)
442 		return (ENOENT);
443 
444 	hwgrpp->dih_n_groups = 0;
445 	err = mac_open_by_linkid(hwgrpp->dih_linkid, &mh);
446 	if (err != 0)
447 		goto done;
448 
449 	hip = (dld_hwgrpinfo_t *)
450 	    ((uchar_t *)arg + sizeof (dld_ioc_hwgrpget_t));
451 	bytes_left = hwgrpp->dih_size;
452 
453 	rgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_RX);
454 	/* display the default group information first */
455 	if (rgrpnum > 0) {
456 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
457 			err = ENOSPC;
458 			goto done;
459 		}
460 
461 		bzero(&hwgrp, sizeof (hwgrp));
462 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
463 		    sizeof (hwgrp.dhi_link_name));
464 		mac_get_hwrxgrp_info(mh, 0, &hwgrp.dhi_grp_num,
465 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
466 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
467 		if (hwgrp.dhi_n_rings != 0) {
468 			if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
469 				err = EFAULT;
470 				goto done;
471 			}
472 		}
473 		hip++;
474 		totgrps++;
475 		bytes_left -= sizeof (dld_hwgrpinfo_t);
476 	}
477 
478 	tgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_TX);
479 	/* display the default group information first */
480 	if (tgrpnum > 0) {
481 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
482 			err = ENOSPC;
483 			goto done;
484 		}
485 
486 		bzero(&hwgrp, sizeof (hwgrp));
487 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
488 		    sizeof (hwgrp.dhi_link_name));
489 		mac_get_hwtxgrp_info(mh, tgrpnum - 1, &hwgrp.dhi_grp_num,
490 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
491 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
492 		if (hwgrp.dhi_n_rings != 0) {
493 			if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
494 				err = EFAULT;
495 				goto done;
496 			}
497 		}
498 		hip++;
499 		totgrps++;
500 		bytes_left -= sizeof (dld_hwgrpinfo_t);
501 	}
502 
503 	/* Rest of the rx groups */
504 	for (i = 1; i < rgrpnum; i++) {
505 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
506 			err = ENOSPC;
507 			goto done;
508 		}
509 
510 		bzero(&hwgrp, sizeof (hwgrp));
511 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
512 		    sizeof (hwgrp.dhi_link_name));
513 		mac_get_hwrxgrp_info(mh, i, &hwgrp.dhi_grp_num,
514 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
515 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
516 		if (hwgrp.dhi_n_rings == 0)
517 			continue;
518 		if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
519 			err = EFAULT;
520 			goto done;
521 		}
522 
523 		hip++;
524 		totgrps++;
525 		bytes_left -= sizeof (dld_hwgrpinfo_t);
526 	}
527 
528 	/* Rest of the tx group */
529 	tgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_TX);
530 	for (i = 0; i < tgrpnum - 1; i++) {
531 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
532 			err = ENOSPC;
533 			goto done;
534 		}
535 
536 		bzero(&hwgrp, sizeof (hwgrp));
537 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
538 		    sizeof (hwgrp.dhi_link_name));
539 		mac_get_hwtxgrp_info(mh, i, &hwgrp.dhi_grp_num,
540 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
541 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
542 		if (hwgrp.dhi_n_rings == 0)
543 			continue;
544 		if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
545 			err = EFAULT;
546 			goto done;
547 		}
548 
549 		hip++;
550 		totgrps++;
551 		bytes_left -= sizeof (dld_hwgrpinfo_t);
552 	}
553 
554 done:
555 	if (mh != NULL)
556 		dld_mac_close(mh);
557 	if (err == 0)
558 		hwgrpp->dih_n_groups = totgrps;
559 	return (err);
560 }
561 
562 /* ARGSUSED */
563 static int
drv_ioc_macaddrget(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)564 drv_ioc_macaddrget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
565 {
566 	dld_ioc_macaddrget_t	*magp = karg;
567 	dld_macaddrinfo_t	mai, *maip;
568 	mac_handle_t		mh = NULL;
569 	int			i, err;
570 	uint_t			bytes_left;
571 	boolean_t		is_used;
572 	zoneid_t		zoneid = crgetzoneid(cred);
573 
574 	if (zoneid != GLOBAL_ZONEID &&
575 	    zone_check_datalink(&zoneid, magp->dig_linkid) != 0)
576 		return (ENOENT);
577 
578 	magp->dig_count = 0;
579 	err = mac_open_by_linkid(magp->dig_linkid, &mh);
580 	if (err != 0)
581 		goto done;
582 
583 	maip = (dld_macaddrinfo_t *)
584 	    ((uchar_t *)arg + sizeof (dld_ioc_macaddrget_t));
585 	bytes_left = magp->dig_size;
586 
587 	for (i = 0; i < mac_addr_factory_num(mh) + 1; i++) {
588 		if (sizeof (dld_macaddrinfo_t) > bytes_left) {
589 			err = ENOSPC;
590 			goto done;
591 		}
592 
593 		bzero(&mai, sizeof (mai));
594 
595 		if (i == 0) {
596 			/* primary MAC address */
597 			mac_unicast_primary_get(mh, mai.dmi_addr);
598 			mai.dmi_addrlen = mac_addr_len(mh);
599 			mac_unicast_primary_info(mh, mai.dmi_client_name,
600 			    &is_used);
601 		} else {
602 			/* factory MAC address slot */
603 			mac_addr_factory_value(mh, i, mai.dmi_addr,
604 			    &mai.dmi_addrlen, mai.dmi_client_name, &is_used);
605 		}
606 
607 		mai.dmi_slot = i;
608 		if (is_used)
609 			mai.dmi_flags |= DLDIOCMACADDR_USED;
610 
611 		if (copyout(&mai, maip, sizeof (mai)) != 0) {
612 			err = EFAULT;
613 			goto done;
614 		}
615 
616 		maip++;
617 		bytes_left -= sizeof (dld_macaddrinfo_t);
618 	}
619 
620 done:
621 	if (mh != NULL)
622 		dld_mac_close(mh);
623 	if (err == 0)
624 		magp->dig_count = mac_addr_factory_num(mh) + 1;
625 	return (err);
626 }
627 
628 /*
629  * Calculate caller's allowed permissions to a property.
630  */
631 static uint_t
dld_macprop_perm_mask(dls_dl_handle_t dlh,const dld_ioc_macprop_t * prop,const cred_t * cred)632 dld_macprop_perm_mask(dls_dl_handle_t dlh, const dld_ioc_macprop_t *prop,
633     const cred_t *cred)
634 {
635 	const zoneid_t zid = crgetzoneid(cred);
636 	const boolean_t is_owner = zid == dls_devnet_getownerzid(dlh);
637 	const boolean_t is_in_zone = zid == dls_devnet_getzid(dlh);
638 	const boolean_t is_in_global = zid == GLOBAL_ZONEID;
639 	uint_t mask = 0;
640 
641 	/* If the zone owns the datalink device, it should have full access. */
642 	if (is_owner) {
643 		mask = MAC_PROP_PERM_RW;
644 	}
645 
646 	switch (prop->pr_num) {
647 	case MAC_PROP_MTU:
648 	case MAC_PROP_STATUS:
649 		/*
650 		 * These properties remain readable inside a zone which has been
651 		 * assigned (but does not own) a datalink device.
652 		 */
653 		if (is_in_zone) {
654 			mask |= MAC_PROP_PERM_READ;
655 		}
656 		break;
657 	case MAC_PROP_ZONE:
658 		/*
659 		 * Actors outside global zone are not allowed to change the zone
660 		 * ownership property.
661 		 */
662 		if (!is_in_global) {
663 			mask &= ~MAC_PROP_PERM_WRITE;
664 		}
665 		break;
666 	default:
667 		break;
668 	}
669 	return (mask);
670 }
671 
672 /*
673  * DLDIOC_SET/GETMACPROP
674  */
675 static int
drv_ioc_prop_common(dld_ioc_macprop_t * prop,intptr_t arg,boolean_t set,cred_t * cred,int mode)676 drv_ioc_prop_common(dld_ioc_macprop_t *prop, intptr_t arg, boolean_t set,
677     cred_t *cred, int mode)
678 {
679 	int			err = EINVAL;
680 	dls_dl_handle_t		dlh = NULL;
681 	dls_link_t		*dlp = NULL;
682 	mac_perim_handle_t	mph = NULL;
683 	dld_ioc_macprop_t	*kprop;
684 	datalink_id_t		linkid;
685 	datalink_class_t	class;
686 	uint_t			dsize;
687 
688 	/*
689 	 * We only use pr_valsize from prop, as the caller only did a
690 	 * copyin() for sizeof (dld_ioc_prop_t), which doesn't cover
691 	 * the property data.  We copyin the full dld_ioc_prop_t
692 	 * including the data into kprop down below.
693 	 */
694 	dsize = sizeof (dld_ioc_macprop_t) + prop->pr_valsize - 1;
695 	if (dsize < prop->pr_valsize)
696 		return (EINVAL);
697 
698 	/*
699 	 * The property data is variable size, so we need to allocate
700 	 * a buffer for kernel use as this data was not part of the
701 	 * prop allocation and copyin() done by the framework.
702 	 */
703 	if ((kprop = kmem_alloc(dsize, KM_NOSLEEP)) == NULL)
704 		return (ENOMEM);
705 
706 	if (ddi_copyin((void *)arg, kprop, dsize, mode) != 0) {
707 		err = EFAULT;
708 		goto done;
709 	}
710 
711 	linkid = kprop->pr_linkid;
712 
713 	if (set) {
714 		if ((err = dls_mgmt_get_linkinfo(linkid, NULL, &class, NULL,
715 		    NULL)) != 0 || (err = drv_ioc_checkprivs(class, cred)) != 0)
716 			goto done;
717 	}
718 
719 	if ((err = dls_devnet_hold_tmp(linkid, &dlh)) != 0)
720 		goto done;
721 	if ((err = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
722 		goto done;
723 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
724 		goto done;
725 
726 	const uint_t perm_req = set ? MAC_PROP_PERM_RW : MAC_PROP_PERM_READ;
727 	const uint_t perm_mask = dld_macprop_perm_mask(dlh, kprop, cred);
728 	if ((perm_req & perm_mask) != perm_req) {
729 		err = EACCES;
730 		goto done;
731 	}
732 
733 	if (!mac_prop_check_size(kprop->pr_num, kprop->pr_valsize,
734 	    kprop->pr_flags & DLD_PROP_POSSIBLE)) {
735 		err = ENOBUFS;
736 		goto done;
737 	}
738 
739 	switch (kprop->pr_num) {
740 	case MAC_PROP_ZONE:
741 		if (set) {
742 			dld_ioc_zid_t *dzp = (dld_ioc_zid_t *)kprop->pr_val;
743 
744 			err = dls_devnet_setzid(dlh, dzp->diz_zid);
745 		} else {
746 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
747 			(*(zoneid_t *)kprop->pr_val) = dls_devnet_getzid(dlh);
748 		}
749 		break;
750 	case MAC_PROP_AUTOPUSH: {
751 		struct dlautopush *dlap = (struct dlautopush *)kprop->pr_val;
752 
753 		if (set) {
754 			if (kprop->pr_valsize != 0)
755 				err = drv_ioc_setap(linkid, dlap);
756 			else
757 				err = drv_ioc_clrap(linkid);
758 		} else {
759 			/*
760 			 * You might think that the earlier call to
761 			 * mac_prop_check_size() should catch this but
762 			 * it can't. The autopush prop uses 0 as a
763 			 * sentinel value to clear the prop. This
764 			 * check ensures we don't allow a get with a
765 			 * valsize of 0.
766 			 */
767 			if (kprop->pr_valsize == 0) {
768 				err = ENOBUFS;
769 				goto done;
770 			}
771 
772 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
773 			err = drv_ioc_getap(linkid, dlap);
774 		}
775 		break;
776 	}
777 	case MAC_PROP_TAGMODE:
778 		if (set) {
779 			link_tagmode_t mode = *(link_tagmode_t *)kprop->pr_val;
780 
781 			if (mode != LINK_TAGMODE_VLANONLY &&
782 			    mode != LINK_TAGMODE_NORMAL) {
783 				err = EINVAL;
784 			} else {
785 				dlp->dl_tagmode = mode;
786 				err = 0;
787 			}
788 		} else {
789 			*(link_tagmode_t *)kprop->pr_val = dlp->dl_tagmode;
790 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
791 			err = 0;
792 		}
793 		break;
794 	default: {
795 		mac_propval_range_t *rangep = NULL;
796 		void *default_val = NULL;
797 		uint_t default_size = 0;
798 
799 		/* set a property value */
800 		if (set) {
801 			err = mac_set_prop(dlp->dl_mh, kprop->pr_num,
802 			    kprop->pr_name, kprop->pr_val, kprop->pr_valsize);
803 			break;
804 		}
805 
806 		/*
807 		 * Get the property value, default, or possible value
808 		 * depending on flags passed from the user.
809 		 */
810 
811 		/* a property has RW permissions by default */
812 		kprop->pr_perm_flags = MAC_PROP_PERM_RW;
813 
814 		if (kprop->pr_flags & DLD_PROP_POSSIBLE) {
815 			rangep = (mac_propval_range_t *)kprop->pr_val;
816 
817 			/*
818 			 * fail if rangep is not aligned to first
819 			 * member of mac_propval_range_t.
820 			 */
821 			ASSERT(IS_P2ALIGNED(rangep, sizeof (uint_t)));
822 		} else if (kprop->pr_flags & DLD_PROP_DEFAULT) {
823 			default_val = kprop->pr_val;
824 			default_size = kprop->pr_valsize;
825 		}
826 
827 		/*
828 		 * Always return the permissions, and optionally return
829 		 * the default value or possible values range.
830 		 */
831 		err = mac_prop_info(dlp->dl_mh, kprop->pr_num, kprop->pr_name,
832 		    default_val, default_size, rangep, &kprop->pr_perm_flags);
833 		if (err != 0)
834 			goto done;
835 
836 		if (default_val == NULL && rangep == NULL) {
837 			err = mac_get_prop(dlp->dl_mh, kprop->pr_num,
838 			    kprop->pr_name, kprop->pr_val, kprop->pr_valsize);
839 		}
840 	}
841 	}
842 
843 	/* Properly communicate allowed permissions */
844 	kprop->pr_perm_flags &= perm_mask;
845 
846 done:
847 	if (!set && ddi_copyout(kprop, (void *)arg, dsize, mode) != 0)
848 		err = EFAULT;
849 
850 	if (dlp != NULL)
851 		dls_link_rele(dlp);
852 
853 	if (mph != NULL) {
854 		int32_t	cpuid;
855 		void	*mdip = NULL;
856 
857 		if (dlp != NULL && set && err == 0) {
858 			cpuid = mac_client_intr_cpu(dlp->dl_mch);
859 			mdip = mac_get_devinfo(dlp->dl_mh);
860 		}
861 
862 		mac_perim_exit(mph);
863 
864 		if (mdip != NULL && cpuid != -1)
865 			mac_client_set_intr_cpu(mdip, dlp->dl_mch, cpuid);
866 	}
867 
868 	if (dlh != NULL)
869 		dls_devnet_rele_tmp(dlh);
870 
871 	if (kprop != NULL)
872 		kmem_free(kprop, dsize);
873 	return (err);
874 }
875 
876 /* ARGSUSED */
877 static int
drv_ioc_setprop(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)878 drv_ioc_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
879 {
880 	return (drv_ioc_prop_common(karg, arg, B_TRUE, cred, mode));
881 }
882 
883 /* ARGSUSED */
884 static int
drv_ioc_getprop(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)885 drv_ioc_getprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
886 {
887 	return (drv_ioc_prop_common(karg, arg, B_FALSE, cred, mode));
888 }
889 
890 /*
891  * DLDIOC_RENAME.
892  *
893  * This function handles two cases of link renaming. See more in comments above
894  * dls_devnet_rename().
895  */
896 /* ARGSUSED */
897 static int
drv_ioc_rename(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)898 drv_ioc_rename(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
899 {
900 	dld_ioc_rename_t	*dir = karg;
901 	mod_hash_key_t		key;
902 	mod_hash_val_t		val;
903 	zoneid_t		zoneid = crgetzoneid(cred);
904 	datalink_class_t	class;
905 	int			err;
906 
907 	if (zoneid != GLOBAL_ZONEID &&
908 	    (zone_check_datalink(&zoneid, dir->dir_linkid1) != 0 ||
909 	    dir->dir_linkid2 != DATALINK_INVALID_LINKID &&
910 	    zone_check_datalink(&zoneid, dir->dir_linkid2) != 0))
911 		return (ENOENT);
912 
913 	if ((err = dls_mgmt_get_linkinfo(dir->dir_linkid1, NULL, &class, NULL,
914 	    NULL)) != 0)
915 		return (err);
916 
917 	if ((err = drv_ioc_checkprivs(class, cred)) != 0)
918 		return (err);
919 
920 	if ((err = dls_devnet_rename(dir->dir_linkid1, dir->dir_linkid2,
921 	    dir->dir_link)) != 0)
922 		return (err);
923 
924 	if (dir->dir_linkid2 == DATALINK_INVALID_LINKID)
925 		return (0);
926 
927 	/*
928 	 * if dir_linkid2 is not DATALINK_INVALID_LINKID, it means this
929 	 * renaming request is to rename a valid physical link (dir_linkid1)
930 	 * to a "removed" physical link (dir_linkid2, which is removed by DR
931 	 * or during system shutdown). In this case, the link (specified by
932 	 * dir_linkid1) would inherit all the configuration of dir_linkid2,
933 	 * and dir_linkid1 and its configuration would be lost.
934 	 *
935 	 * Remove per-link autopush configuration of dir_linkid1 in this case.
936 	 */
937 	key = (mod_hash_key_t)(uintptr_t)dir->dir_linkid1;
938 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
939 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
940 		rw_exit(&dld_ap_hash_lock);
941 		return (0);
942 	}
943 
944 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
945 	kmem_free(val, sizeof (dld_ap_t));
946 	rw_exit(&dld_ap_hash_lock);
947 	return (0);
948 }
949 
950 static int
drv_ioc_setap(datalink_id_t linkid,struct dlautopush * dlap)951 drv_ioc_setap(datalink_id_t linkid, struct dlautopush *dlap)
952 {
953 	dld_ap_t	*dap;
954 	int		i;
955 	mod_hash_key_t	key;
956 
957 	if (dlap->dap_npush == 0 || dlap->dap_npush > MAXAPUSH)
958 		return (EINVAL);
959 
960 	/*
961 	 * Validate that the specified list of modules exist.
962 	 */
963 	for (i = 0; i < dlap->dap_npush; i++) {
964 		if (fmodsw_find(dlap->dap_aplist[i], FMODSW_LOAD) == NULL)
965 			return (EINVAL);
966 	}
967 
968 
969 	key = (mod_hash_key_t)(uintptr_t)linkid;
970 
971 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
972 	if (mod_hash_find(dld_ap_hashp, key, (mod_hash_val_t *)&dap) != 0) {
973 		dap = kmem_zalloc(sizeof (dld_ap_t), KM_NOSLEEP);
974 		if (dap == NULL) {
975 			rw_exit(&dld_ap_hash_lock);
976 			return (ENOMEM);
977 		}
978 
979 		dap->da_linkid = linkid;
980 		VERIFY(mod_hash_insert(dld_ap_hashp, key,
981 		    (mod_hash_val_t)dap) == 0);
982 	}
983 
984 	/*
985 	 * Update the configuration.
986 	 */
987 	dap->da_anchor = dlap->dap_anchor;
988 	dap->da_npush = dlap->dap_npush;
989 	for (i = 0; i < dlap->dap_npush; i++) {
990 		(void) strlcpy(dap->da_aplist[i], dlap->dap_aplist[i],
991 		    FMNAMESZ + 1);
992 	}
993 	rw_exit(&dld_ap_hash_lock);
994 
995 	return (0);
996 }
997 
998 static int
drv_ioc_getap(datalink_id_t linkid,struct dlautopush * dlap)999 drv_ioc_getap(datalink_id_t linkid, struct dlautopush *dlap)
1000 {
1001 	dld_ap_t	*dap;
1002 	int		i;
1003 
1004 	rw_enter(&dld_ap_hash_lock, RW_READER);
1005 	if (mod_hash_find(dld_ap_hashp,
1006 	    (mod_hash_key_t)(uintptr_t)linkid,
1007 	    (mod_hash_val_t *)&dap) != 0) {
1008 		rw_exit(&dld_ap_hash_lock);
1009 		dlap->dap_npush = 0;
1010 		return (0);
1011 	}
1012 
1013 	/*
1014 	 * Retrieve the configuration.
1015 	 */
1016 	dlap->dap_anchor = dap->da_anchor;
1017 	dlap->dap_npush = dap->da_npush;
1018 	for (i = 0; i < dap->da_npush; i++) {
1019 		(void) strlcpy(dlap->dap_aplist[i], dap->da_aplist[i],
1020 		    FMNAMESZ + 1);
1021 	}
1022 	rw_exit(&dld_ap_hash_lock);
1023 
1024 	return (0);
1025 }
1026 
1027 static int
drv_ioc_clrap(datalink_id_t linkid)1028 drv_ioc_clrap(datalink_id_t linkid)
1029 {
1030 	mod_hash_val_t	val;
1031 	mod_hash_key_t	key;
1032 
1033 	key = (mod_hash_key_t)(uintptr_t)linkid;
1034 
1035 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
1036 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
1037 		rw_exit(&dld_ap_hash_lock);
1038 		return (0);
1039 	}
1040 
1041 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
1042 	kmem_free(val, sizeof (dld_ap_t));
1043 	rw_exit(&dld_ap_hash_lock);
1044 	return (0);
1045 }
1046 
1047 /*
1048  * DLDIOC_DOORSERVER
1049  */
1050 /* ARGSUSED */
1051 static int
drv_ioc_doorserver(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1052 drv_ioc_doorserver(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1053 {
1054 	dld_ioc_door_t	*did = karg;
1055 
1056 	return (dls_mgmt_door_set(did->did_start_door));
1057 }
1058 
1059 /*
1060  * DLDIOC_USAGELOG
1061  */
1062 /* ARGSUSED */
1063 static int
drv_ioc_usagelog(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1064 drv_ioc_usagelog(void *karg, intptr_t arg, int mode, cred_t *cred,
1065     int *rvalp)
1066 {
1067 	dld_ioc_usagelog_t	*log_info = (dld_ioc_usagelog_t *)karg;
1068 	int			err = 0;
1069 
1070 	if (log_info->ul_type < MAC_LOGTYPE_LINK ||
1071 	    log_info->ul_type > MAC_LOGTYPE_FLOW)
1072 		return (EINVAL);
1073 
1074 	if (log_info->ul_onoff) {
1075 		err = mac_start_logusage(log_info->ul_type,
1076 		    log_info->ul_interval);
1077 	} else {
1078 		mac_stop_logusage(log_info->ul_type);
1079 	}
1080 	return (err);
1081 }
1082 
1083 /*
1084  * Process a DLDIOC_ADDFLOW request.
1085  */
1086 /* ARGSUSED */
1087 static int
drv_ioc_addflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1088 drv_ioc_addflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1089 {
1090 	dld_ioc_addflow_t	*afp = karg;
1091 
1092 	return (dld_add_flow(afp->af_linkid, afp->af_name,
1093 	    &afp->af_flow_desc, &afp->af_resource_props));
1094 }
1095 
1096 /*
1097  * Process a DLDIOC_REMOVEFLOW request.
1098  */
1099 /* ARGSUSED */
1100 static int
drv_ioc_removeflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1101 drv_ioc_removeflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1102 {
1103 	dld_ioc_removeflow_t	*rfp = karg;
1104 
1105 	return (dld_remove_flow(rfp->rf_name));
1106 }
1107 
1108 /*
1109  * Process a DLDIOC_MODIFYFLOW request.
1110  */
1111 /* ARGSUSED */
1112 static int
drv_ioc_modifyflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1113 drv_ioc_modifyflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1114 {
1115 	dld_ioc_modifyflow_t	*mfp = karg;
1116 
1117 	return (dld_modify_flow(mfp->mf_name, &mfp->mf_resource_props));
1118 }
1119 
1120 /*
1121  * Process a DLDIOC_WALKFLOW request.
1122  */
1123 /* ARGSUSED */
1124 static int
drv_ioc_walkflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1125 drv_ioc_walkflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1126 {
1127 	dld_ioc_walkflow_t	*wfp = karg;
1128 
1129 	return (dld_walk_flow(wfp, arg, cred));
1130 }
1131 
1132 /*
1133  * Check for GLDv3 autopush information.  There are three cases:
1134  *
1135  *   1. If devp points to a GLDv3 datalink and it has autopush configuration,
1136  *	fill dlap in with that information and return 0.
1137  *
1138  *   2. If devp points to a GLDv3 datalink but it doesn't have autopush
1139  *	configuration, then replace devp with the physical device (if one
1140  *	exists) and return 1.  This allows stropen() to find the old-school
1141  *	per-driver autopush configuration.  (For softmac, the result is that
1142  *	the softmac dev_t is replaced with the legacy device's dev_t).
1143  *
1144  *   3. If neither of the above apply, don't touch the args and return -1.
1145  */
1146 int
dld_autopush(dev_t * devp,struct dlautopush * dlap)1147 dld_autopush(dev_t *devp, struct dlautopush *dlap)
1148 {
1149 	dld_ap_t	*dap;
1150 	datalink_id_t	linkid;
1151 	dev_t		phydev;
1152 
1153 	if (!GLDV3_DRV(getmajor(*devp)))
1154 		return (-1);
1155 
1156 	/*
1157 	 * Find the linkid by the link's dev_t.
1158 	 */
1159 	if (dls_devnet_dev2linkid(*devp, &linkid) != 0)
1160 		return (-1);
1161 
1162 	/*
1163 	 * Find the autopush configuration associated with the linkid.
1164 	 */
1165 	rw_enter(&dld_ap_hash_lock, RW_READER);
1166 	if (mod_hash_find(dld_ap_hashp, (mod_hash_key_t)(uintptr_t)linkid,
1167 	    (mod_hash_val_t *)&dap) == 0) {
1168 		*dlap = dap->da_ap;
1169 		rw_exit(&dld_ap_hash_lock);
1170 		return (0);
1171 	}
1172 	rw_exit(&dld_ap_hash_lock);
1173 
1174 	if (dls_devnet_phydev(linkid, &phydev) != 0)
1175 		return (-1);
1176 
1177 	*devp = phydev;
1178 	return (1);
1179 }
1180 
1181 /*
1182  * Secure objects implementation
1183  */
1184 
1185 /* ARGSUSED */
1186 static int
drv_secobj_ctor(void * buf,void * arg,int kmflag)1187 drv_secobj_ctor(void *buf, void *arg, int kmflag)
1188 {
1189 	bzero(buf, sizeof (dld_secobj_t));
1190 	return (0);
1191 }
1192 
1193 static void
drv_secobj_init(void)1194 drv_secobj_init(void)
1195 {
1196 	rw_init(&drv_secobj_lock, NULL, RW_DEFAULT, NULL);
1197 	drv_secobj_cachep = kmem_cache_create("drv_secobj_cache",
1198 	    sizeof (dld_secobj_t), 0, drv_secobj_ctor, NULL,
1199 	    NULL, NULL, NULL, 0);
1200 	drv_secobj_hash = mod_hash_create_extended("drv_secobj_hash",
1201 	    SECOBJ_WEP_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
1202 	    mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
1203 }
1204 
1205 static void
drv_secobj_fini(void)1206 drv_secobj_fini(void)
1207 {
1208 	mod_hash_destroy_hash(drv_secobj_hash);
1209 	kmem_cache_destroy(drv_secobj_cachep);
1210 	rw_destroy(&drv_secobj_lock);
1211 }
1212 
1213 /* ARGSUSED */
1214 static int
drv_ioc_secobj_set(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1215 drv_ioc_secobj_set(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1216 {
1217 	dld_ioc_secobj_set_t	*ssp = karg;
1218 	dld_secobj_t		*sobjp, *objp;
1219 	int			err;
1220 
1221 	sobjp = &ssp->ss_obj;
1222 
1223 	if (sobjp->so_class != DLD_SECOBJ_CLASS_WEP &&
1224 	    sobjp->so_class != DLD_SECOBJ_CLASS_WPA)
1225 		return (EINVAL);
1226 
1227 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0' ||
1228 	    sobjp->so_len > DLD_SECOBJ_VAL_MAX)
1229 		return (EINVAL);
1230 
1231 	rw_enter(&drv_secobj_lock, RW_WRITER);
1232 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sobjp->so_name,
1233 	    (mod_hash_val_t *)&objp);
1234 	if (err == 0) {
1235 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) != 0) {
1236 			rw_exit(&drv_secobj_lock);
1237 			return (EEXIST);
1238 		}
1239 	} else {
1240 		ASSERT(err == MH_ERR_NOTFOUND);
1241 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) == 0) {
1242 			rw_exit(&drv_secobj_lock);
1243 			return (ENOENT);
1244 		}
1245 		objp = kmem_cache_alloc(drv_secobj_cachep, KM_SLEEP);
1246 		(void) strlcpy(objp->so_name, sobjp->so_name,
1247 		    DLD_SECOBJ_NAME_MAX);
1248 
1249 		VERIFY(mod_hash_insert(drv_secobj_hash,
1250 		    (mod_hash_key_t)objp->so_name, (mod_hash_val_t)objp) == 0);
1251 	}
1252 	bcopy(sobjp->so_val, objp->so_val, sobjp->so_len);
1253 	objp->so_len = sobjp->so_len;
1254 	objp->so_class = sobjp->so_class;
1255 	rw_exit(&drv_secobj_lock);
1256 	return (0);
1257 }
1258 
1259 typedef struct dld_secobj_state {
1260 	uint_t		ss_free;
1261 	uint_t		ss_count;
1262 	int		ss_rc;
1263 	int		ss_mode;
1264 	dld_secobj_t	*ss_objp;
1265 } dld_secobj_state_t;
1266 
1267 /* ARGSUSED */
1268 static uint_t
drv_secobj_walker(mod_hash_key_t key,mod_hash_val_t * val,void * arg)1269 drv_secobj_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
1270 {
1271 	dld_secobj_state_t	*statep = arg;
1272 	dld_secobj_t		*sobjp = (dld_secobj_t *)val;
1273 
1274 	if (statep->ss_free < sizeof (dld_secobj_t)) {
1275 		statep->ss_rc = ENOSPC;
1276 		return (MH_WALK_TERMINATE);
1277 	}
1278 	if (ddi_copyout(sobjp, statep->ss_objp, sizeof (*sobjp),
1279 	    statep->ss_mode) != 0) {
1280 		statep->ss_rc = EFAULT;
1281 		return (MH_WALK_TERMINATE);
1282 	}
1283 	statep->ss_objp++;
1284 	statep->ss_free -= sizeof (dld_secobj_t);
1285 	statep->ss_count++;
1286 	return (MH_WALK_CONTINUE);
1287 }
1288 
1289 /* ARGSUSED */
1290 static int
drv_ioc_secobj_get(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1291 drv_ioc_secobj_get(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1292 {
1293 	dld_ioc_secobj_get_t	*sgp = karg;
1294 	dld_secobj_t		*sobjp, *objp;
1295 	int			err;
1296 
1297 	sobjp = &sgp->sg_obj;
1298 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1299 		return (EINVAL);
1300 
1301 	rw_enter(&drv_secobj_lock, RW_READER);
1302 	if (sobjp->so_name[0] != '\0') {
1303 		err = mod_hash_find(drv_secobj_hash,
1304 		    (mod_hash_key_t)sobjp->so_name, (mod_hash_val_t *)&objp);
1305 		if (err != 0) {
1306 			ASSERT(err == MH_ERR_NOTFOUND);
1307 			rw_exit(&drv_secobj_lock);
1308 			return (ENOENT);
1309 		}
1310 		bcopy(objp->so_val, sobjp->so_val, objp->so_len);
1311 		sobjp->so_len = objp->so_len;
1312 		sobjp->so_class = objp->so_class;
1313 		sgp->sg_count = 1;
1314 	} else {
1315 		dld_secobj_state_t	state;
1316 
1317 		state.ss_free = sgp->sg_size - sizeof (dld_ioc_secobj_get_t);
1318 		state.ss_count = 0;
1319 		state.ss_rc = 0;
1320 		state.ss_mode = mode;
1321 		state.ss_objp = (dld_secobj_t *)((uchar_t *)arg +
1322 		    sizeof (dld_ioc_secobj_get_t));
1323 
1324 		mod_hash_walk(drv_secobj_hash, drv_secobj_walker, &state);
1325 		if (state.ss_rc != 0) {
1326 			rw_exit(&drv_secobj_lock);
1327 			return (state.ss_rc);
1328 		}
1329 		sgp->sg_count = state.ss_count;
1330 	}
1331 	rw_exit(&drv_secobj_lock);
1332 	return (0);
1333 }
1334 
1335 /* ARGSUSED */
1336 static int
drv_ioc_secobj_unset(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1337 drv_ioc_secobj_unset(void *karg, intptr_t arg, int mode, cred_t *cred,
1338     int *rvalp)
1339 {
1340 	dld_ioc_secobj_unset_t	*sup = karg;
1341 	dld_secobj_t		*objp;
1342 	mod_hash_val_t		val;
1343 	int			err;
1344 
1345 	if (sup->su_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1346 		return (EINVAL);
1347 
1348 	rw_enter(&drv_secobj_lock, RW_WRITER);
1349 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1350 	    (mod_hash_val_t *)&objp);
1351 	if (err != 0) {
1352 		ASSERT(err == MH_ERR_NOTFOUND);
1353 		rw_exit(&drv_secobj_lock);
1354 		return (ENOENT);
1355 	}
1356 	VERIFY(mod_hash_remove(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1357 	    (mod_hash_val_t *)&val) == 0);
1358 	ASSERT(objp == (dld_secobj_t *)val);
1359 
1360 	kmem_cache_free(drv_secobj_cachep, objp);
1361 	rw_exit(&drv_secobj_lock);
1362 	return (0);
1363 }
1364 
1365 /* ARGSUSED */
1366 static int
drv_ioc_gettran(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1367 drv_ioc_gettran(void *karg, intptr_t arg, int mode, cred_t *cred,
1368     int *rvalp)
1369 {
1370 	int			ret = 0;
1371 	mac_perim_handle_t	mph = NULL;
1372 	dls_dl_handle_t		dlh = NULL;
1373 	dls_link_t		*dlp = NULL;
1374 	dld_ioc_gettran_t	*dgt = karg;
1375 
1376 	if ((ret = dls_devnet_hold_tmp(dgt->dgt_linkid, &dlh)) != 0)
1377 		goto done;
1378 
1379 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1380 		goto done;
1381 
1382 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1383 		goto done;
1384 
1385 	/*
1386 	 * Make sure that this link belongs to the zone.
1387 	 */
1388 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1389 		ret = ENOENT;
1390 		goto done;
1391 	}
1392 
1393 	if (dgt->dgt_tran_id == DLDIOC_GETTRAN_GETNTRAN) {
1394 		ret = mac_transceiver_count(dlp->dl_mh, &dgt->dgt_tran_id);
1395 	} else {
1396 		ret = mac_transceiver_info(dlp->dl_mh, dgt->dgt_tran_id,
1397 		    &dgt->dgt_present, &dgt->dgt_usable);
1398 	}
1399 
1400 done:
1401 	if (dlp != NULL)
1402 		dls_link_rele(dlp);
1403 
1404 	if (mph != NULL)
1405 		mac_perim_exit(mph);
1406 
1407 	if (dlh != NULL)
1408 		dls_devnet_rele_tmp(dlh);
1409 
1410 	return (ret);
1411 }
1412 
1413 /* ARGSUSED */
1414 static int
drv_ioc_readtran(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1415 drv_ioc_readtran(void *karg, intptr_t arg, int mode, cred_t *cred,
1416     int *rvalp)
1417 {
1418 	int			ret = 0;
1419 	mac_perim_handle_t	mph = NULL;
1420 	dls_dl_handle_t		dlh = NULL;
1421 	dls_link_t		*dlp = NULL;
1422 	dld_ioc_tranio_t	*dti = karg;
1423 	uint8_t			buf[256];
1424 	size_t			nr;
1425 
1426 	/*
1427 	 * Be strict for the moment
1428 	 */
1429 	if (dti->dti_nbytes != 256 || dti->dti_off != 0)
1430 		return (EINVAL);
1431 
1432 	if ((ret = dls_devnet_hold_tmp(dti->dti_linkid, &dlh)) != 0)
1433 		goto done;
1434 
1435 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1436 		goto done;
1437 
1438 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1439 		goto done;
1440 
1441 	/*
1442 	 * Make sure that this link belongs to the zone.
1443 	 */
1444 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1445 		ret = ENOENT;
1446 		goto done;
1447 	}
1448 
1449 	bzero(buf, sizeof (buf));
1450 	if ((ret = mac_transceiver_read(dlp->dl_mh, dti->dti_tran_id,
1451 	    dti->dti_page, buf, dti->dti_nbytes, dti->dti_off, &nr)) == 0) {
1452 		dti->dti_nbytes = nr;
1453 		ret = ddi_copyout(buf, (void *)(uintptr_t)dti->dti_buf,
1454 		    sizeof (buf), mode);
1455 	}
1456 
1457 done:
1458 	if (dlp != NULL)
1459 		dls_link_rele(dlp);
1460 
1461 	if (mph != NULL)
1462 		mac_perim_exit(mph);
1463 
1464 	if (dlh != NULL)
1465 		dls_devnet_rele_tmp(dlh);
1466 
1467 	return (ret);
1468 }
1469 
1470 /* ARGSUSED */
1471 static int
drv_ioc_getled(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1472 drv_ioc_getled(void *karg, intptr_t arg, int mode, cred_t *cred,
1473     int *rvalp)
1474 {
1475 	int			ret = 0;
1476 	mac_perim_handle_t	mph = NULL;
1477 	dls_dl_handle_t		dlh = NULL;
1478 	dls_link_t		*dlp = NULL;
1479 	dld_ioc_led_t		*dil = karg;
1480 
1481 	if ((mode & FREAD) == 0)
1482 		return (EBADF);
1483 
1484 	if ((ret = dls_devnet_hold_tmp(dil->dil_linkid, &dlh)) != 0)
1485 		goto done;
1486 
1487 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1488 		goto done;
1489 
1490 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1491 		goto done;
1492 
1493 	/*
1494 	 * Make sure that this link belongs to the zone.
1495 	 */
1496 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1497 		ret = ENOENT;
1498 		goto done;
1499 	}
1500 
1501 	ret = mac_led_get(dlp->dl_mh, &dil->dil_supported, &dil->dil_active);
1502 
1503 done:
1504 	if (dlp != NULL)
1505 		dls_link_rele(dlp);
1506 
1507 	if (mph != NULL)
1508 		mac_perim_exit(mph);
1509 
1510 	if (dlh != NULL)
1511 		dls_devnet_rele_tmp(dlh);
1512 
1513 	return (ret);
1514 }
1515 
1516 /* ARGSUSED */
1517 static int
drv_ioc_setled(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1518 drv_ioc_setled(void *karg, intptr_t arg, int mode, cred_t *cred,
1519     int *rvalp)
1520 {
1521 	int			ret = 0;
1522 	mac_perim_handle_t	mph = NULL;
1523 	dls_dl_handle_t		dlh = NULL;
1524 	dls_link_t		*dlp = NULL;
1525 	dld_ioc_led_t		*dil = karg;
1526 
1527 	if ((mode & FWRITE) == 0)
1528 		return (EBADF);
1529 
1530 	if ((ret = dls_devnet_hold_tmp(dil->dil_linkid, &dlh)) != 0)
1531 		goto done;
1532 
1533 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1534 		goto done;
1535 
1536 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1537 		goto done;
1538 
1539 	/*
1540 	 * Make sure that this link belongs to the zone.
1541 	 */
1542 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1543 		ret = ENOENT;
1544 		goto done;
1545 	}
1546 
1547 	ret = mac_led_set(dlp->dl_mh, dil->dil_active);
1548 
1549 done:
1550 	if (dlp != NULL)
1551 		dls_link_rele(dlp);
1552 
1553 	if (mph != NULL)
1554 		mac_perim_exit(mph);
1555 
1556 	if (dlh != NULL)
1557 		dls_devnet_rele_tmp(dlh);
1558 
1559 	return (ret);
1560 }
1561 
1562 
1563 /*
1564  * Note that ioctls that modify links have a NULL di_priv_func(), as
1565  * privileges can only be checked after we know the class of the link being
1566  * modified (due to class-specific fine-grained privileges such as
1567  * sys_iptun_config).
1568  */
1569 static dld_ioc_info_t drv_ioc_list[] = {
1570 	{DLDIOC_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_attr_t),
1571 	    drv_ioc_attr, NULL},
1572 	{DLDIOC_PHYS_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_phys_attr_t),
1573 	    drv_ioc_phys_attr, NULL},
1574 	{DLDIOC_SECOBJ_SET, DLDCOPYIN, sizeof (dld_ioc_secobj_set_t),
1575 	    drv_ioc_secobj_set, secpolicy_dl_config},
1576 	{DLDIOC_SECOBJ_GET, DLDCOPYINOUT, sizeof (dld_ioc_secobj_get_t),
1577 	    drv_ioc_secobj_get, secpolicy_dl_config},
1578 	{DLDIOC_SECOBJ_UNSET, DLDCOPYIN, sizeof (dld_ioc_secobj_unset_t),
1579 	    drv_ioc_secobj_unset, secpolicy_dl_config},
1580 	{DLDIOC_DOORSERVER, DLDCOPYIN, sizeof (dld_ioc_door_t),
1581 	    drv_ioc_doorserver, secpolicy_dl_config},
1582 	{DLDIOC_RENAME, DLDCOPYIN, sizeof (dld_ioc_rename_t),
1583 	    drv_ioc_rename, NULL},
1584 	{DLDIOC_MACADDRGET, DLDCOPYINOUT, sizeof (dld_ioc_macaddrget_t),
1585 	    drv_ioc_macaddrget, NULL},
1586 	{DLDIOC_ADDFLOW, DLDCOPYIN, sizeof (dld_ioc_addflow_t),
1587 	    drv_ioc_addflow, secpolicy_dl_config},
1588 	{DLDIOC_REMOVEFLOW, DLDCOPYIN, sizeof (dld_ioc_removeflow_t),
1589 	    drv_ioc_removeflow, secpolicy_dl_config},
1590 	{DLDIOC_MODIFYFLOW, DLDCOPYIN, sizeof (dld_ioc_modifyflow_t),
1591 	    drv_ioc_modifyflow, secpolicy_dl_config},
1592 	{DLDIOC_WALKFLOW, DLDCOPYINOUT, sizeof (dld_ioc_walkflow_t),
1593 	    drv_ioc_walkflow, NULL},
1594 	{DLDIOC_USAGELOG, DLDCOPYIN, sizeof (dld_ioc_usagelog_t),
1595 	    drv_ioc_usagelog, secpolicy_dl_config},
1596 	{DLDIOC_SETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1597 	    drv_ioc_setprop, NULL},
1598 	{DLDIOC_GETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1599 	    drv_ioc_getprop, NULL},
1600 	{DLDIOC_GETHWGRP, DLDCOPYINOUT, sizeof (dld_ioc_hwgrpget_t),
1601 	    drv_ioc_hwgrpget, NULL},
1602 	{DLDIOC_GETTRAN, DLDCOPYINOUT, sizeof (dld_ioc_gettran_t),
1603 	    drv_ioc_gettran, NULL },
1604 	{DLDIOC_READTRAN, DLDCOPYINOUT, sizeof (dld_ioc_tranio_t),
1605 	    drv_ioc_readtran, NULL },
1606 	{DLDIOC_GETLED, DLDCOPYINOUT, sizeof (dld_ioc_led_t),
1607 	    drv_ioc_getled, NULL },
1608 	{DLDIOC_SETLED, DLDCOPYIN, sizeof (dld_ioc_led_t),
1609 	    drv_ioc_setled, secpolicy_dl_config}
1610 };
1611 
1612 typedef struct dld_ioc_modentry {
1613 	uint16_t	dim_modid;	/* Top 16 bits of ioctl command */
1614 	char		*dim_modname;	/* Module to be loaded */
1615 	int		ctrl_node_inst;	/* Ctrl node instance */
1616 	dld_ioc_info_t	*dim_list;	/* array of ioctl structures */
1617 	uint_t		dim_count;	/* number of elements in dim_list */
1618 } dld_ioc_modentry_t;
1619 
1620 /*
1621  * For all modules except for dld, dim_list and dim_count are assigned
1622  * when the modules register their ioctls in dld_ioc_register().  We
1623  * can statically initialize dld's ioctls in-line here; there's no
1624  * need for it to call dld_ioc_register() itself. ctrl_node_inst controls
1625  * whether an instance of the device will be held or the driver. If set to
1626  * a non-negative integer, device instance specified in ctrl_node_inst will
1627  * be held; so dld_ioc_register() _must_ be called in xxx_attach() routine of
1628  * the driver. If set to -1, driver will be held; so dld_ioc_register() _must_
1629  * be called in xxx_init() routine of the driver.
1630  */
1631 static dld_ioc_modentry_t dld_ioc_modtable[] = {
1632 	{DLD_IOC,	"dld", 0, drv_ioc_list, DLDIOCCNT(drv_ioc_list)},
1633 	{AGGR_IOC,	"aggr", 0, NULL, 0},
1634 	{VNIC_IOC,	"vnic",	0, NULL, 0},
1635 	{SIMNET_IOC,	"simnet", 0, NULL, 0},
1636 	{BRIDGE_IOC,	"bridge", 0, NULL, 0},
1637 	{IPTUN_IOC,	"iptun", 0, NULL, 0},
1638 	{IBPART_IOC,	"ibp", -1, NULL, 0},
1639 	{OVERLAY_IOC,	"overlay", 0, NULL, 0}
1640 };
1641 #define	DLDIOC_CNT	\
1642 	(sizeof (dld_ioc_modtable) / sizeof (dld_ioc_modentry_t))
1643 
1644 static dld_ioc_modentry_t *
dld_ioc_findmod(uint16_t modid)1645 dld_ioc_findmod(uint16_t modid)
1646 {
1647 	int	i;
1648 
1649 	for (i = 0; i < DLDIOC_CNT; i++) {
1650 		if (modid == dld_ioc_modtable[i].dim_modid)
1651 			return (&dld_ioc_modtable[i]);
1652 	}
1653 	return (NULL);
1654 }
1655 
1656 int
dld_ioc_register(uint16_t modid,dld_ioc_info_t * list,uint_t count)1657 dld_ioc_register(uint16_t modid, dld_ioc_info_t *list, uint_t count)
1658 {
1659 	dld_ioc_modentry_t *dim = dld_ioc_findmod(modid);
1660 
1661 	if (dim == NULL)
1662 		return (ENOENT);
1663 
1664 	dim->dim_list = list;
1665 	dim->dim_count = count;
1666 	return (0);
1667 }
1668 
1669 void
dld_ioc_unregister(uint16_t modid)1670 dld_ioc_unregister(uint16_t modid)
1671 {
1672 	VERIFY(dld_ioc_register(modid, NULL, 0) == 0);
1673 }
1674 
1675 /*
1676  * The general design with GLDv3 ioctls is that all ioctls issued
1677  * through /dev/dld go through this drv_ioctl() function.  This
1678  * function handles all ioctls on behalf of modules listed in
1679  * dld_ioc_modtable.
1680  *
1681  * When an ioctl is received, this function looks for the associated
1682  * module-id-specific ioctl information using dld_ioc_findmod(). The
1683  * call to ddi_hold_driver() or ddi_hold_devi_by_instance() on the
1684  * associated device will cause the kernel module responsible for the
1685  * ioctl to be loaded if it's not already loaded, which should result
1686  * in that module calling dld_ioc_register(), thereby filling in the
1687  * dim_list containing the details for the ioctl being processed.
1688  *
1689  * This function can then perform operations such as copyin() data and
1690  * do credential checks based on the registered ioctl information,
1691  * then issue the callback function di_func() registered by the
1692  * responsible module.  Upon return, the appropriate copyout()
1693  * operation can be performed and the operation completes.
1694  */
1695 /* ARGSUSED */
1696 static int
drv_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * cred,int * rvalp)1697 drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1698 {
1699 	dld_ioc_modentry_t *dim;
1700 	dld_ioc_info_t	*info;
1701 	dev_info_t	*dip = NULL;
1702 	struct dev_ops	*dops = NULL;
1703 	major_t		major;
1704 	void		*buf = NULL;
1705 	size_t		sz;
1706 	int		i, err;
1707 
1708 	if ((dim = dld_ioc_findmod(DLD_IOC_MODID(cmd))) == NULL)
1709 		return (ENOTSUP);
1710 
1711 	major = ddi_name_to_major(dim->dim_modname);
1712 
1713 	if (dim->ctrl_node_inst == -1) {
1714 		/*
1715 		 * No dedicated instance to process ioctls.
1716 		 * dld_ioc_register() is called in xxx_init().
1717 		 */
1718 		dops = ddi_hold_driver(major);
1719 	} else {
1720 		/*
1721 		 * Dedicated instance to handle ioctl.
1722 		 * dld_ioc_register() is called in xxx_attach().
1723 		 */
1724 		dip = ddi_hold_devi_by_instance(major, dim->ctrl_node_inst, 0);
1725 	}
1726 
1727 	if ((dip == NULL && dops == NULL) || dim->dim_list == NULL) {
1728 		err = ENODEV;
1729 		goto done;
1730 	}
1731 
1732 	for (i = 0; i < dim->dim_count; i++) {
1733 		if (cmd == dim->dim_list[i].di_cmd)
1734 			break;
1735 	}
1736 	if (i == dim->dim_count) {
1737 		err = ENOTSUP;
1738 		goto done;
1739 	}
1740 
1741 	info = &dim->dim_list[i];
1742 
1743 	if (info->di_priv_func != NULL &&
1744 	    (err = info->di_priv_func(cred)) != 0)
1745 		goto done;
1746 
1747 	sz = info->di_argsize;
1748 	if ((buf = kmem_zalloc(sz, KM_NOSLEEP)) == NULL) {
1749 		err = ENOMEM;
1750 		goto done;
1751 	}
1752 
1753 	if ((info->di_flags & DLDCOPYIN) &&
1754 	    ddi_copyin((void *)arg, buf, sz, mode) != 0) {
1755 		err = EFAULT;
1756 		goto done;
1757 	}
1758 
1759 	err = info->di_func(buf, arg, mode, cred, rvalp);
1760 
1761 	if ((info->di_flags & DLDCOPYOUT) &&
1762 	    ddi_copyout(buf, (void *)arg, sz, mode) != 0 && err == 0)
1763 		err = EFAULT;
1764 
1765 done:
1766 	if (buf != NULL)
1767 		kmem_free(buf, sz);
1768 	if (dip != NULL)
1769 		ddi_release_devi(dip);
1770 	if (dops != NULL)
1771 		ddi_rele_driver(major);
1772 	return (err);
1773 }
1774