xref: /onnv-gate/usr/src/uts/common/fs/dev/sdev_ptsops.c (revision 3898:c788126f2a20)
12621Sllai1 /*
22621Sllai1  * CDDL HEADER START
32621Sllai1  *
42621Sllai1  * The contents of this file are subject to the terms of the
52621Sllai1  * Common Development and Distribution License (the "License").
62621Sllai1  * You may not use this file except in compliance with the License.
72621Sllai1  *
82621Sllai1  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
92621Sllai1  * or http://www.opensolaris.org/os/licensing.
102621Sllai1  * See the License for the specific language governing permissions
112621Sllai1  * and limitations under the License.
122621Sllai1  *
132621Sllai1  * When distributing Covered Code, include this CDDL HEADER in each
142621Sllai1  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
152621Sllai1  * If applicable, add the following below this CDDL HEADER, with the
162621Sllai1  * fields enclosed by brackets "[]" replaced with your own identifying
172621Sllai1  * information: Portions Copyright [yyyy] [name of copyright owner]
182621Sllai1  *
192621Sllai1  * CDDL HEADER END
202621Sllai1  */
212621Sllai1 /*
223442Svikram  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
232621Sllai1  * Use is subject to license terms.
242621Sllai1  */
252621Sllai1 
262621Sllai1 #pragma ident	"%Z%%M%	%I%	%E% SMI"
272621Sllai1 
282621Sllai1 /*
292621Sllai1  * vnode ops for the /dev/pts directory
302621Sllai1  *	The lookup is based on the internal pty table. We also
312621Sllai1  *	override readdir in order to delete pts nodes no longer
322621Sllai1  *	in use.
332621Sllai1  */
342621Sllai1 
352621Sllai1 #include <sys/types.h>
362621Sllai1 #include <sys/param.h>
372621Sllai1 #include <sys/sysmacros.h>
382621Sllai1 #include <sys/sunndi.h>
392621Sllai1 #include <fs/fs_subr.h>
402621Sllai1 #include <sys/fs/dv_node.h>
412621Sllai1 #include <sys/fs/sdev_impl.h>
422621Sllai1 #include <sys/policy.h>
432621Sllai1 #include <sys/ptms.h>
442621Sllai1 #include <sys/stat.h>
45*3898Srsb #include <sys/vfs_opreg.h>
462621Sllai1 
472621Sllai1 #define	DEVPTS_UID_DEFAULT	0
482621Sllai1 #define	DEVPTS_GID_DEFAULT	3
492621Sllai1 #define	DEVPTS_DEVMODE_DEFAULT	(0620)
502621Sllai1 
512621Sllai1 #define	isdigit(ch)	((ch) >= '0' && (ch) <= '9')
522621Sllai1 
532621Sllai1 static vattr_t devpts_vattr = {
542621Sllai1 	AT_TYPE|AT_MODE|AT_UID|AT_GID,		/* va_mask */
552621Sllai1 	VCHR,					/* va_type */
562621Sllai1 	S_IFCHR | DEVPTS_DEVMODE_DEFAULT,	/* va_mode */
572621Sllai1 	DEVPTS_UID_DEFAULT,			/* va_uid */
582621Sllai1 	DEVPTS_GID_DEFAULT,			/* va_gid */
592621Sllai1 	0					/* 0 hereafter */
602621Sllai1 };
612621Sllai1 
622621Sllai1 struct vnodeops		*devpts_vnodeops;
632621Sllai1 
642621Sllai1 struct vnodeops *
652621Sllai1 devpts_getvnodeops(void)
662621Sllai1 {
672621Sllai1 	return (devpts_vnodeops);
682621Sllai1 }
692621Sllai1 
702621Sllai1 /*
712621Sllai1  * Convert string to minor number. Some care must be taken
722621Sllai1  * as we are processing user input. Catch cases like
732621Sllai1  * /dev/pts/4foo and /dev/pts/-1
742621Sllai1  */
752621Sllai1 static int
762621Sllai1 devpts_strtol(const char *nm, minor_t *mp)
772621Sllai1 {
782621Sllai1 	long uminor = 0;
792621Sllai1 	char *endptr = NULL;
802621Sllai1 
812621Sllai1 	if (nm == NULL || !isdigit(*nm))
822621Sllai1 		return (EINVAL);
832621Sllai1 
842621Sllai1 	*mp = 0;
852621Sllai1 	if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
862621Sllai1 	    *endptr != '\0' || uminor < 0) {
872621Sllai1 		return (EINVAL);
882621Sllai1 	}
892621Sllai1 
903133Sjg 	*mp = (minor_t)uminor;
912621Sllai1 	return (0);
922621Sllai1 }
932621Sllai1 
942621Sllai1 /*
952621Sllai1  * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
962621Sllai1  * This serves two purposes
972621Sllai1  *	- only valid pts nodes are returned during lookup() and readdir().
982621Sllai1  *	- since pts sdev_nodes are not actively destroyed when a pty goes
992621Sllai1  *	  away, we use the validator to do deferred cleanup i.e. when such
1002621Sllai1  *	  nodes are encountered during subsequent lookup() and readdir().
1012621Sllai1  */
1022621Sllai1 /*ARGSUSED*/
1032621Sllai1 int
1042621Sllai1 devpts_validate(struct sdev_node *dv)
1052621Sllai1 {
1062621Sllai1 	minor_t min;
1072621Sllai1 	uid_t uid;
1082621Sllai1 	gid_t gid;
1092621Sllai1 	timestruc_t now;
1102621Sllai1 	char *nm = dv->sdev_name;
1112621Sllai1 
1122621Sllai1 	ASSERT(!(dv->sdev_flags & SDEV_STALE));
1132621Sllai1 	ASSERT(dv->sdev_state == SDEV_READY);
1142621Sllai1 
1152621Sllai1 	/* validate only READY nodes */
1162621Sllai1 	if (dv->sdev_state != SDEV_READY) {
1172621Sllai1 		sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
1182621Sllai1 		    nm, (void *)dv));
1192621Sllai1 		return (SDEV_VTOR_SKIP);
1202621Sllai1 	}
1212621Sllai1 
1222621Sllai1 	if (devpts_strtol(nm, &min) != 0) {
1232621Sllai1 		sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
1242621Sllai1 		return (SDEV_VTOR_INVALID);
1252621Sllai1 	}
1262621Sllai1 
1272621Sllai1 	/*
1282621Sllai1 	 * Check if pts driver is attached
1292621Sllai1 	 */
1302621Sllai1 	if (ptms_slave_attached() == (major_t)-1) {
1312621Sllai1 		sdcmn_err7(("devpts_validate: slave not attached\n"));
1322621Sllai1 		return (SDEV_VTOR_INVALID);
1332621Sllai1 	}
1342621Sllai1 
1352621Sllai1 	if (ptms_minor_valid(min, &uid, &gid) == 0) {
1362621Sllai1 		if (ptms_minor_exists(min)) {
1372621Sllai1 			sdcmn_err7(("devpts_validate: valid in different zone "
1382621Sllai1 			    "%s\n", nm));
1392621Sllai1 			return (SDEV_VTOR_SKIP);
1402621Sllai1 		} else {
1412621Sllai1 			sdcmn_err7(("devpts_validate: %s not valid pty\n",
1422621Sllai1 			    nm));
1432621Sllai1 			return (SDEV_VTOR_INVALID);
1442621Sllai1 		}
1452621Sllai1 	}
1462621Sllai1 
1472621Sllai1 	ASSERT(dv->sdev_attr);
1482621Sllai1 	if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
1492621Sllai1 		ASSERT(uid >= 0);
1502621Sllai1 		ASSERT(gid >= 0);
1512621Sllai1 		dv->sdev_attr->va_uid = uid;
1522621Sllai1 		dv->sdev_attr->va_gid = gid;
1532621Sllai1 		gethrestime(&now);
1542621Sllai1 		dv->sdev_attr->va_atime = now;
1552621Sllai1 		dv->sdev_attr->va_mtime = now;
1562621Sllai1 		dv->sdev_attr->va_ctime = now;
1572621Sllai1 		sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
1582621Sllai1 	}
1592621Sllai1 
1602621Sllai1 	return (SDEV_VTOR_VALID);
1612621Sllai1 }
1622621Sllai1 
1632621Sllai1 /*
1642621Sllai1  * This callback is invoked from devname_lookup_func() to create
1652621Sllai1  * a pts entry when the node is not found in the cache.
1662621Sllai1  */
1672621Sllai1 /*ARGSUSED*/
1682621Sllai1 static int
1692621Sllai1 devpts_create_rvp(struct sdev_node *ddv, char *nm,
1702621Sllai1     void **arg, cred_t *cred, void *whatever, char *whichever)
1712621Sllai1 {
1722621Sllai1 	minor_t min;
1732621Sllai1 	major_t maj;
1742621Sllai1 	uid_t uid;
1752621Sllai1 	gid_t gid;
1762621Sllai1 	timestruc_t now;
1772621Sllai1 	struct vattr *vap = (struct vattr *)arg;
1782621Sllai1 
1792621Sllai1 	if (devpts_strtol(nm, &min) != 0) {
1802621Sllai1 		sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
1812621Sllai1 		return (-1);
1822621Sllai1 	}
1832621Sllai1 
1842621Sllai1 	/*
1852621Sllai1 	 * Check if pts driver is attached and if it is
1862621Sllai1 	 * get the major number.
1872621Sllai1 	 */
1882621Sllai1 	maj = ptms_slave_attached();
1892621Sllai1 	if (maj == (major_t)-1) {
1902621Sllai1 		sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
1912621Sllai1 		return (-1);
1922621Sllai1 	}
1932621Sllai1 
1942621Sllai1 	/*
1952621Sllai1 	 * Only allow creation of ptys allocated to our zone
1962621Sllai1 	 */
1972621Sllai1 	if (!ptms_minor_valid(min, &uid, &gid)) {
1982621Sllai1 		sdcmn_err7(("devpts_create_rvp: %s not valid pty"
1992621Sllai1 		    "or not valid in this zone\n", nm));
2002621Sllai1 		return (-1);
2012621Sllai1 	}
2022621Sllai1 
2032621Sllai1 
2042621Sllai1 	/*
2052621Sllai1 	 * This is a valid pty (at least at this point in time).
2062621Sllai1 	 * Create the node by setting the attribute. The rest
2072621Sllai1 	 * is taken care of by devname_lookup_func().
2082621Sllai1 	 */
2092621Sllai1 	*vap = devpts_vattr;
2102621Sllai1 	vap->va_rdev = makedevice(maj, min);
2112621Sllai1 	ASSERT(uid >= 0);
2122621Sllai1 	ASSERT(gid >= 0);
2132621Sllai1 	vap->va_uid = uid;
2142621Sllai1 	vap->va_gid = gid;
2152621Sllai1 	gethrestime(&now);
2162621Sllai1 	vap->va_atime = now;
2172621Sllai1 	vap->va_mtime = now;
2182621Sllai1 	vap->va_ctime = now;
2192621Sllai1 
2202621Sllai1 	return (0);
2212621Sllai1 }
2222621Sllai1 
2232621Sllai1 /*
2242621Sllai1  * Clean pts sdev_nodes that are no longer valid.
2252621Sllai1  */
2262621Sllai1 static void
2272621Sllai1 devpts_prunedir(struct sdev_node *ddv)
2282621Sllai1 {
2292621Sllai1 	struct vnode *vp;
2302621Sllai1 	struct sdev_node *dv, *next = NULL;
2312621Sllai1 	int (*vtor)(struct sdev_node *) = NULL;
2322621Sllai1 
2332621Sllai1 	ASSERT(ddv->sdev_flags & SDEV_VTOR);
2342621Sllai1 
2352621Sllai1 	vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
2362621Sllai1 	ASSERT(vtor);
2372621Sllai1 
2382621Sllai1 	if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
2392621Sllai1 		rw_exit(&ddv->sdev_contents);
2402621Sllai1 		rw_enter(&ddv->sdev_contents, RW_WRITER);
2412621Sllai1 	}
2422621Sllai1 
2432621Sllai1 	for (dv = ddv->sdev_dot; dv; dv = next) {
2442621Sllai1 		next = dv->sdev_next;
2452621Sllai1 
2462621Sllai1 		/* skip stale nodes */
2472621Sllai1 		if (dv->sdev_flags & SDEV_STALE)
2482621Sllai1 			continue;
2492621Sllai1 
2502621Sllai1 		/* validate and prune only ready nodes */
2512621Sllai1 		if (dv->sdev_state != SDEV_READY)
2522621Sllai1 			continue;
2532621Sllai1 
2542621Sllai1 		switch (vtor(dv)) {
2552621Sllai1 		case SDEV_VTOR_VALID:
2562621Sllai1 		case SDEV_VTOR_SKIP:
2572621Sllai1 			continue;
2582621Sllai1 		case SDEV_VTOR_INVALID:
2592621Sllai1 			sdcmn_err7(("prunedir: destroy invalid "
2602621Sllai1 			    "node: %s(%p)\n", dv->sdev_name, (void *)dv));
2612621Sllai1 			break;
2622621Sllai1 		}
2632621Sllai1 		vp = SDEVTOV(dv);
2642621Sllai1 		if (vp->v_count > 0)
2652621Sllai1 			continue;
2662621Sllai1 		SDEV_HOLD(dv);
2672621Sllai1 		/* remove the cache node */
2682621Sllai1 		(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
2692621Sllai1 		    SDEV_CACHE_DELETE);
2702621Sllai1 	}
2712621Sllai1 	rw_downgrade(&ddv->sdev_contents);
2722621Sllai1 }
2732621Sllai1 
2742621Sllai1 /*
2752621Sllai1  * Lookup for /dev/pts directory
2762621Sllai1  *	If the entry does not exist, the devpts_create_rvp() callback
2772621Sllai1  *	is invoked to create it. Nodes do not persist across reboot.
2783442Svikram  *
2793442Svikram  * There is a potential denial of service here via
2803442Svikram  * fattach on top of a /dev/pts node - any permission changes
2813442Svikram  * applied to the node, apply to the fattached file and not
2823442Svikram  * to the underlying pts node. As a result when the previous
2833442Svikram  * user fdetaches, the pts node is still owned by the previous
2843442Svikram  * owner. To prevent this we don't allow fattach() on top of a pts
2853442Svikram  * node. This is done by a modification in the namefs filesystem
2863442Svikram  * where we check if the underlying node has the /dev/pts vnodeops.
2873442Svikram  * We do this via VOP_REALVP() on the underlying specfs node.
2883442Svikram  * sdev_nodes currently don't have a realvp. If a realvp is ever
2893442Svikram  * created for sdev_nodes, then VOP_REALVP() will return the
2903442Svikram  * actual realvp (possibly a ufs vnode). This will defeat the check
2913442Svikram  * in namefs code which checks if VOP_REALVP() returns a devpts
2923442Svikram  * node. We add an ASSERT here in /dev/pts lookup() to check for
2933442Svikram  * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
2943442Svikram  * change the code in the namefs filesystem code (in nm_mount()) to
2953442Svikram  * access the realvp of the specfs node directly instead of using
2963442Svikram  * VOP_REALVP().
2972621Sllai1  */
2982621Sllai1 /*ARGSUSED3*/
2992621Sllai1 static int
3002621Sllai1 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
3012621Sllai1     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred)
3022621Sllai1 {
3032621Sllai1 	struct sdev_node *sdvp = VTOSDEV(dvp);
3042621Sllai1 	struct sdev_node *dv;
3053442Svikram 	struct vnode *rvp = NULL;
3062621Sllai1 	int error;
3072621Sllai1 
3082621Sllai1 	error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
3092621Sllai1 	    SDEV_VATTR);
3102621Sllai1 
3112621Sllai1 	if (error == 0) {
3122621Sllai1 		switch ((*vpp)->v_type) {
3132621Sllai1 		case VCHR:
3142621Sllai1 			dv = VTOSDEV(VTOS(*vpp)->s_realvp);
3153442Svikram 			ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp) == ENOSYS);
3162621Sllai1 			break;
3172621Sllai1 		case VDIR:
3182621Sllai1 			dv = VTOSDEV(*vpp);
3192621Sllai1 			break;
3202621Sllai1 		default:
3212621Sllai1 			cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
3222621Sllai1 			    "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
3232621Sllai1 			break;
3242621Sllai1 		}
3252621Sllai1 		ASSERT(SDEV_HELD(dv));
3262621Sllai1 	}
3272621Sllai1 
3282621Sllai1 	return (error);
3292621Sllai1 }
3302621Sllai1 
3312621Sllai1 /*
3322621Sllai1  * We allow create to find existing nodes
3332621Sllai1  *	- if the node doesn't exist - EROFS
3342621Sllai1  *	- creating an existing dir read-only succeeds, otherwise EISDIR
3352621Sllai1  *	- exclusive creates fail - EEXIST
3362621Sllai1  */
3372621Sllai1 /*ARGSUSED2*/
3382621Sllai1 static int
3392621Sllai1 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
3402621Sllai1     int mode, struct vnode **vpp, struct cred *cred, int flag)
3412621Sllai1 {
3422621Sllai1 	int error;
3432621Sllai1 	struct vnode *vp;
3442621Sllai1 
3452621Sllai1 	*vpp = NULL;
3462621Sllai1 
3472621Sllai1 	error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred);
3482621Sllai1 	if (error == 0) {
3492621Sllai1 		if (excl == EXCL)
3502621Sllai1 			error = EEXIST;
3512621Sllai1 		else if (vp->v_type == VDIR && (mode & VWRITE))
3522621Sllai1 			error = EISDIR;
3532621Sllai1 		else
3542621Sllai1 			error = VOP_ACCESS(vp, mode, 0, cred);
3552621Sllai1 
3562621Sllai1 		if (error) {
3572621Sllai1 			VN_RELE(vp);
3582621Sllai1 		} else
3592621Sllai1 			*vpp = vp;
3602621Sllai1 	} else if (error == ENOENT) {
3612621Sllai1 		error = EROFS;
3622621Sllai1 	}
3632621Sllai1 
3642621Sllai1 	return (error);
3652621Sllai1 }
3662621Sllai1 
3672621Sllai1 /*
3682621Sllai1  * Display all instantiated pts (slave) device nodes.
3692621Sllai1  * A /dev/pts entry will be created only after the first lookup of the slave
3702621Sllai1  * device succeeds.
3712621Sllai1  */
3722621Sllai1 static int
3732621Sllai1 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
3742621Sllai1     int *eofp)
3752621Sllai1 {
3762621Sllai1 	struct sdev_node *sdvp = VTOSDEV(dvp);
3772621Sllai1 	if (uiop->uio_offset == 0) {
3782621Sllai1 		devpts_prunedir(sdvp);
3792621Sllai1 	}
3802621Sllai1 
3812621Sllai1 	return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
3822621Sllai1 }
3832621Sllai1 
3842621Sllai1 
3852621Sllai1 static int
3862621Sllai1 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
3872621Sllai1 {
3882621Sllai1 	ASSERT((protocol & AT_UID) || (protocol & AT_GID));
3892621Sllai1 	ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
3902621Sllai1 	    vap->va_uid, vap->va_gid);
3912621Sllai1 	return (0);
3922621Sllai1 
3932621Sllai1 }
3942621Sllai1 
3953748Sjg /*ARGSUSED4*/
3962621Sllai1 static int
3972621Sllai1 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
3983748Sjg     struct cred *cred, caller_context_t *ctp)
3992621Sllai1 {
4002621Sllai1 	ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
4012621Sllai1 	return (devname_setattr_func(vp, vap, flags, cred,
4022621Sllai1 		    devpts_set_id, AT_UID|AT_GID));
4032621Sllai1 }
4042621Sllai1 
4053442Svikram 
4062621Sllai1 /*
4072621Sllai1  * We override lookup and readdir to build entries based on the
4082621Sllai1  * in kernel pty table. Also override setattr/setsecattr to
4092621Sllai1  * avoid persisting permissions.
4102621Sllai1  */
4112621Sllai1 const fs_operation_def_t devpts_vnodeops_tbl[] = {
412*3898Srsb 	VOPNAME_READDIR,	{ .vop_readdir = devpts_readdir },
413*3898Srsb 	VOPNAME_LOOKUP,		{ .vop_lookup = devpts_lookup },
414*3898Srsb 	VOPNAME_CREATE,		{ .vop_create = devpts_create },
415*3898Srsb 	VOPNAME_SETATTR,	{ .vop_setattr = devpts_setattr },
416*3898Srsb 	VOPNAME_REMOVE,		{ .error = fs_nosys },
417*3898Srsb 	VOPNAME_MKDIR,		{ .error = fs_nosys },
418*3898Srsb 	VOPNAME_RMDIR,		{ .error = fs_nosys },
419*3898Srsb 	VOPNAME_SYMLINK,	{ .error = fs_nosys },
420*3898Srsb 	VOPNAME_SETSECATTR,	{ .error = fs_nosys },
421*3898Srsb 	NULL,			NULL
4222621Sllai1 };
423