Home | History | Annotate | Download | only in dev
      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 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * vnode ops for the /dev/pts directory
     28  *	The lookup is based on the internal pty table. We also
     29  *	override readdir in order to delete pts nodes no longer
     30  *	in use.
     31  */
     32 
     33 #include <sys/types.h>
     34 #include <sys/param.h>
     35 #include <sys/sysmacros.h>
     36 #include <sys/sunndi.h>
     37 #include <fs/fs_subr.h>
     38 #include <sys/fs/dv_node.h>
     39 #include <sys/fs/sdev_impl.h>
     40 #include <sys/policy.h>
     41 #include <sys/ptms.h>
     42 #include <sys/stat.h>
     43 #include <sys/vfs_opreg.h>
     44 
     45 #define	DEVPTS_UID_DEFAULT	0
     46 #define	DEVPTS_GID_DEFAULT	3
     47 #define	DEVPTS_DEVMODE_DEFAULT	(0620)
     48 
     49 #define	isdigit(ch)	((ch) >= '0' && (ch) <= '9')
     50 
     51 static vattr_t devpts_vattr = {
     52 	AT_TYPE|AT_MODE|AT_UID|AT_GID,		/* va_mask */
     53 	VCHR,					/* va_type */
     54 	S_IFCHR | DEVPTS_DEVMODE_DEFAULT,	/* va_mode */
     55 	DEVPTS_UID_DEFAULT,			/* va_uid */
     56 	DEVPTS_GID_DEFAULT,			/* va_gid */
     57 	0					/* 0 hereafter */
     58 };
     59 
     60 struct vnodeops		*devpts_vnodeops;
     61 
     62 struct vnodeops *
     63 devpts_getvnodeops(void)
     64 {
     65 	return (devpts_vnodeops);
     66 }
     67 
     68 /*
     69  * Convert string to minor number. Some care must be taken
     70  * as we are processing user input. Catch cases like
     71  * /dev/pts/4foo and /dev/pts/-1
     72  */
     73 static int
     74 devpts_strtol(const char *nm, minor_t *mp)
     75 {
     76 	long uminor = 0;
     77 	char *endptr = NULL;
     78 
     79 	if (nm == NULL || !isdigit(*nm))
     80 		return (EINVAL);
     81 
     82 	*mp = 0;
     83 	if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
     84 	    *endptr != '\0' || uminor < 0) {
     85 		return (EINVAL);
     86 	}
     87 
     88 	*mp = (minor_t)uminor;
     89 	return (0);
     90 }
     91 
     92 /*
     93  * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
     94  * This serves two purposes
     95  *	- only valid pts nodes are returned during lookup() and readdir().
     96  *	- since pts sdev_nodes are not actively destroyed when a pty goes
     97  *	  away, we use the validator to do deferred cleanup i.e. when such
     98  *	  nodes are encountered during subsequent lookup() and readdir().
     99  */
    100 /*ARGSUSED*/
    101 int
    102 devpts_validate(struct sdev_node *dv)
    103 {
    104 	minor_t min;
    105 	uid_t uid;
    106 	gid_t gid;
    107 	timestruc_t now;
    108 	char *nm = dv->sdev_name;
    109 
    110 	ASSERT(!(dv->sdev_flags & SDEV_STALE));
    111 	ASSERT(dv->sdev_state == SDEV_READY);
    112 
    113 	/* validate only READY nodes */
    114 	if (dv->sdev_state != SDEV_READY) {
    115 		sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
    116 		    nm, (void *)dv));
    117 		return (SDEV_VTOR_SKIP);
    118 	}
    119 
    120 	if (devpts_strtol(nm, &min) != 0) {
    121 		sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
    122 		return (SDEV_VTOR_INVALID);
    123 	}
    124 
    125 	/*
    126 	 * Check if pts driver is attached
    127 	 */
    128 	if (ptms_slave_attached() == (major_t)-1) {
    129 		sdcmn_err7(("devpts_validate: slave not attached\n"));
    130 		return (SDEV_VTOR_INVALID);
    131 	}
    132 
    133 	if (ptms_minor_valid(min, &uid, &gid) == 0) {
    134 		if (ptms_minor_exists(min)) {
    135 			sdcmn_err7(("devpts_validate: valid in different zone "
    136 			    "%s\n", nm));
    137 			return (SDEV_VTOR_SKIP);
    138 		} else {
    139 			sdcmn_err7(("devpts_validate: %s not valid pty\n",
    140 			    nm));
    141 			return (SDEV_VTOR_INVALID);
    142 		}
    143 	}
    144 
    145 	ASSERT(dv->sdev_attr);
    146 	if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
    147 		dv->sdev_attr->va_uid = uid;
    148 		dv->sdev_attr->va_gid = gid;
    149 		gethrestime(&now);
    150 		dv->sdev_attr->va_atime = now;
    151 		dv->sdev_attr->va_mtime = now;
    152 		dv->sdev_attr->va_ctime = now;
    153 		sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
    154 	}
    155 
    156 	return (SDEV_VTOR_VALID);
    157 }
    158 
    159 /*
    160  * This callback is invoked from devname_lookup_func() to create
    161  * a pts entry when the node is not found in the cache.
    162  */
    163 /*ARGSUSED*/
    164 static int
    165 devpts_create_rvp(struct sdev_node *ddv, char *nm,
    166     void **arg, cred_t *cred, void *whatever, char *whichever)
    167 {
    168 	minor_t min;
    169 	major_t maj;
    170 	uid_t uid;
    171 	gid_t gid;
    172 	timestruc_t now;
    173 	struct vattr *vap = (struct vattr *)arg;
    174 
    175 	if (devpts_strtol(nm, &min) != 0) {
    176 		sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
    177 		return (-1);
    178 	}
    179 
    180 	/*
    181 	 * Check if pts driver is attached and if it is
    182 	 * get the major number.
    183 	 */
    184 	maj = ptms_slave_attached();
    185 	if (maj == (major_t)-1) {
    186 		sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
    187 		return (-1);
    188 	}
    189 
    190 	/*
    191 	 * Only allow creation of ptys allocated to our zone
    192 	 */
    193 	if (!ptms_minor_valid(min, &uid, &gid)) {
    194 		sdcmn_err7(("devpts_create_rvp: %s not valid pty"
    195 		    "or not valid in this zone\n", nm));
    196 		return (-1);
    197 	}
    198 
    199 
    200 	/*
    201 	 * This is a valid pty (at least at this point in time).
    202 	 * Create the node by setting the attribute. The rest
    203 	 * is taken care of by devname_lookup_func().
    204 	 */
    205 	*vap = devpts_vattr;
    206 	vap->va_rdev = makedevice(maj, min);
    207 	vap->va_uid = uid;
    208 	vap->va_gid = gid;
    209 	gethrestime(&now);
    210 	vap->va_atime = now;
    211 	vap->va_mtime = now;
    212 	vap->va_ctime = now;
    213 
    214 	return (0);
    215 }
    216 
    217 /*
    218  * Clean pts sdev_nodes that are no longer valid.
    219  */
    220 static void
    221 devpts_prunedir(struct sdev_node *ddv)
    222 {
    223 	struct vnode *vp;
    224 	struct sdev_node *dv, *next = NULL;
    225 	int (*vtor)(struct sdev_node *) = NULL;
    226 
    227 	ASSERT(ddv->sdev_flags & SDEV_VTOR);
    228 
    229 	vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
    230 	ASSERT(vtor);
    231 
    232 	if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
    233 		rw_exit(&ddv->sdev_contents);
    234 		rw_enter(&ddv->sdev_contents, RW_WRITER);
    235 	}
    236 
    237 	for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
    238 		next = SDEV_NEXT_ENTRY(ddv, dv);
    239 
    240 		/* validate and prune only ready nodes */
    241 		if (dv->sdev_state != SDEV_READY)
    242 			continue;
    243 
    244 		switch (vtor(dv)) {
    245 		case SDEV_VTOR_VALID:
    246 		case SDEV_VTOR_SKIP:
    247 			continue;
    248 		case SDEV_VTOR_INVALID:
    249 		case SDEV_VTOR_STALE:
    250 			sdcmn_err7(("prunedir: destroy invalid "
    251 			    "node: %s(%p)\n", dv->sdev_name, (void *)dv));
    252 			break;
    253 		}
    254 		vp = SDEVTOV(dv);
    255 		if (vp->v_count > 0)
    256 			continue;
    257 		SDEV_HOLD(dv);
    258 		/* remove the cache node */
    259 		(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
    260 		    SDEV_CACHE_DELETE);
    261 	}
    262 	rw_downgrade(&ddv->sdev_contents);
    263 }
    264 
    265 /*
    266  * Lookup for /dev/pts directory
    267  *	If the entry does not exist, the devpts_create_rvp() callback
    268  *	is invoked to create it. Nodes do not persist across reboot.
    269  *
    270  * There is a potential denial of service here via
    271  * fattach on top of a /dev/pts node - any permission changes
    272  * applied to the node, apply to the fattached file and not
    273  * to the underlying pts node. As a result when the previous
    274  * user fdetaches, the pts node is still owned by the previous
    275  * owner. To prevent this we don't allow fattach() on top of a pts
    276  * node. This is done by a modification in the namefs filesystem
    277  * where we check if the underlying node has the /dev/pts vnodeops.
    278  * We do this via VOP_REALVP() on the underlying specfs node.
    279  * sdev_nodes currently don't have a realvp. If a realvp is ever
    280  * created for sdev_nodes, then VOP_REALVP() will return the
    281  * actual realvp (possibly a ufs vnode). This will defeat the check
    282  * in namefs code which checks if VOP_REALVP() returns a devpts
    283  * node. We add an ASSERT here in /dev/pts lookup() to check for
    284  * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
    285  * change the code in the namefs filesystem code (in nm_mount()) to
    286  * access the realvp of the specfs node directly instead of using
    287  * VOP_REALVP().
    288  */
    289 /*ARGSUSED3*/
    290 static int
    291 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
    292     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
    293     caller_context_t *ct, int *direntflags, pathname_t *realpnp)
    294 {
    295 	struct sdev_node *sdvp = VTOSDEV(dvp);
    296 	struct sdev_node *dv;
    297 	struct vnode *rvp = NULL;
    298 	int error;
    299 
    300 	error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
    301 	    SDEV_VATTR);
    302 
    303 	if (error == 0) {
    304 		switch ((*vpp)->v_type) {
    305 		case VCHR:
    306 			dv = VTOSDEV(VTOS(*vpp)->s_realvp);
    307 			ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
    308 			break;
    309 		case VDIR:
    310 			dv = VTOSDEV(*vpp);
    311 			break;
    312 		default:
    313 			cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
    314 			    "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
    315 			break;
    316 		}
    317 		ASSERT(SDEV_HELD(dv));
    318 	}
    319 
    320 	return (error);
    321 }
    322 
    323 /*
    324  * We allow create to find existing nodes
    325  *	- if the node doesn't exist - EROFS
    326  *	- creating an existing dir read-only succeeds, otherwise EISDIR
    327  *	- exclusive creates fail - EEXIST
    328  */
    329 /*ARGSUSED2*/
    330 static int
    331 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
    332     int mode, struct vnode **vpp, struct cred *cred, int flag,
    333     caller_context_t *ct, vsecattr_t *vsecp)
    334 {
    335 	int error;
    336 	struct vnode *vp;
    337 
    338 	*vpp = NULL;
    339 
    340 	error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL,
    341 	    NULL);
    342 	if (error == 0) {
    343 		if (excl == EXCL)
    344 			error = EEXIST;
    345 		else if (vp->v_type == VDIR && (mode & VWRITE))
    346 			error = EISDIR;
    347 		else
    348 			error = VOP_ACCESS(vp, mode, 0, cred, ct);
    349 
    350 		if (error) {
    351 			VN_RELE(vp);
    352 		} else
    353 			*vpp = vp;
    354 	} else if (error == ENOENT) {
    355 		error = EROFS;
    356 	}
    357 
    358 	return (error);
    359 }
    360 
    361 /*
    362  * Display all instantiated pts (slave) device nodes.
    363  * A /dev/pts entry will be created only after the first lookup of the slave
    364  * device succeeds.
    365  */
    366 /*ARGSUSED4*/
    367 static int
    368 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
    369     int *eofp, caller_context_t *ct, int flags)
    370 {
    371 	struct sdev_node *sdvp = VTOSDEV(dvp);
    372 	if (uiop->uio_offset == 0) {
    373 		devpts_prunedir(sdvp);
    374 	}
    375 
    376 	return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
    377 }
    378 
    379 
    380 static int
    381 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
    382 {
    383 	ASSERT((protocol & AT_UID) || (protocol & AT_GID));
    384 	ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
    385 	    vap->va_uid, vap->va_gid);
    386 	return (0);
    387 
    388 }
    389 
    390 /*ARGSUSED4*/
    391 static int
    392 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
    393     struct cred *cred, caller_context_t *ctp)
    394 {
    395 	ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
    396 	return (devname_setattr_func(vp, vap, flags, cred,
    397 	    devpts_set_id, AT_UID|AT_GID));
    398 }
    399 
    400 
    401 /*
    402  * We override lookup and readdir to build entries based on the
    403  * in kernel pty table. Also override setattr/setsecattr to
    404  * avoid persisting permissions.
    405  */
    406 const fs_operation_def_t devpts_vnodeops_tbl[] = {
    407 	VOPNAME_READDIR,	{ .vop_readdir = devpts_readdir },
    408 	VOPNAME_LOOKUP,		{ .vop_lookup = devpts_lookup },
    409 	VOPNAME_CREATE,		{ .vop_create = devpts_create },
    410 	VOPNAME_SETATTR,	{ .vop_setattr = devpts_setattr },
    411 	VOPNAME_REMOVE,		{ .error = fs_nosys },
    412 	VOPNAME_MKDIR,		{ .error = fs_nosys },
    413 	VOPNAME_RMDIR,		{ .error = fs_nosys },
    414 	VOPNAME_SYMLINK,	{ .error = fs_nosys },
    415 	VOPNAME_SETSECATTR,	{ .error = fs_nosys },
    416 	NULL,			NULL
    417 };
    418