view usr/src/uts/common/fs/dev/sdev_ptsops.c @ 4321:a8930ec16e52

PSARC 2007/064 Unified POSIX and Windows Credentials for Solaris 4994017 data structure sharing between rpcbind and libnsl leads to accidents 6549510 Need the ability to store SIDs in the Solaris cred_t 6549515 PSARC 2007/064: uid_t and gid_t to become unsigned
author casper
date Fri, 25 May 2007 05:43:42 -0700
parents c788126f2a20
children 3047ad28a67b
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * vnode ops for the /dev/pts directory
 *	The lookup is based on the internal pty table. We also
 *	override readdir in order to delete pts nodes no longer
 *	in use.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/sunndi.h>
#include <fs/fs_subr.h>
#include <sys/fs/dv_node.h>
#include <sys/fs/sdev_impl.h>
#include <sys/policy.h>
#include <sys/ptms.h>
#include <sys/stat.h>
#include <sys/vfs_opreg.h>

#define	DEVPTS_UID_DEFAULT	0
#define	DEVPTS_GID_DEFAULT	3
#define	DEVPTS_DEVMODE_DEFAULT	(0620)

#define	isdigit(ch)	((ch) >= '0' && (ch) <= '9')

static vattr_t devpts_vattr = {
	AT_TYPE|AT_MODE|AT_UID|AT_GID,		/* va_mask */
	VCHR,					/* va_type */
	S_IFCHR | DEVPTS_DEVMODE_DEFAULT,	/* va_mode */
	DEVPTS_UID_DEFAULT,			/* va_uid */
	DEVPTS_GID_DEFAULT,			/* va_gid */
	0					/* 0 hereafter */
};

struct vnodeops		*devpts_vnodeops;

struct vnodeops *
devpts_getvnodeops(void)
{
	return (devpts_vnodeops);
}

/*
 * Convert string to minor number. Some care must be taken
 * as we are processing user input. Catch cases like
 * /dev/pts/4foo and /dev/pts/-1
 */
static int
devpts_strtol(const char *nm, minor_t *mp)
{
	long uminor = 0;
	char *endptr = NULL;

	if (nm == NULL || !isdigit(*nm))
		return (EINVAL);

	*mp = 0;
	if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
	    *endptr != '\0' || uminor < 0) {
		return (EINVAL);
	}

	*mp = (minor_t)uminor;
	return (0);
}

/*
 * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
 * This serves two purposes
 *	- only valid pts nodes are returned during lookup() and readdir().
 *	- since pts sdev_nodes are not actively destroyed when a pty goes
 *	  away, we use the validator to do deferred cleanup i.e. when such
 *	  nodes are encountered during subsequent lookup() and readdir().
 */
/*ARGSUSED*/
int
devpts_validate(struct sdev_node *dv)
{
	minor_t min;
	uid_t uid;
	gid_t gid;
	timestruc_t now;
	char *nm = dv->sdev_name;

	ASSERT(!(dv->sdev_flags & SDEV_STALE));
	ASSERT(dv->sdev_state == SDEV_READY);

	/* validate only READY nodes */
	if (dv->sdev_state != SDEV_READY) {
		sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
		    nm, (void *)dv));
		return (SDEV_VTOR_SKIP);
	}

	if (devpts_strtol(nm, &min) != 0) {
		sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
		return (SDEV_VTOR_INVALID);
	}

	/*
	 * Check if pts driver is attached
	 */
	if (ptms_slave_attached() == (major_t)-1) {
		sdcmn_err7(("devpts_validate: slave not attached\n"));
		return (SDEV_VTOR_INVALID);
	}

	if (ptms_minor_valid(min, &uid, &gid) == 0) {
		if (ptms_minor_exists(min)) {
			sdcmn_err7(("devpts_validate: valid in different zone "
			    "%s\n", nm));
			return (SDEV_VTOR_SKIP);
		} else {
			sdcmn_err7(("devpts_validate: %s not valid pty\n",
			    nm));
			return (SDEV_VTOR_INVALID);
		}
	}

	ASSERT(dv->sdev_attr);
	if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
		dv->sdev_attr->va_uid = uid;
		dv->sdev_attr->va_gid = gid;
		gethrestime(&now);
		dv->sdev_attr->va_atime = now;
		dv->sdev_attr->va_mtime = now;
		dv->sdev_attr->va_ctime = now;
		sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
	}

	return (SDEV_VTOR_VALID);
}

/*
 * This callback is invoked from devname_lookup_func() to create
 * a pts entry when the node is not found in the cache.
 */
/*ARGSUSED*/
static int
devpts_create_rvp(struct sdev_node *ddv, char *nm,
    void **arg, cred_t *cred, void *whatever, char *whichever)
{
	minor_t min;
	major_t maj;
	uid_t uid;
	gid_t gid;
	timestruc_t now;
	struct vattr *vap = (struct vattr *)arg;

	if (devpts_strtol(nm, &min) != 0) {
		sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
		return (-1);
	}

	/*
	 * Check if pts driver is attached and if it is
	 * get the major number.
	 */
	maj = ptms_slave_attached();
	if (maj == (major_t)-1) {
		sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
		return (-1);
	}

	/*
	 * Only allow creation of ptys allocated to our zone
	 */
	if (!ptms_minor_valid(min, &uid, &gid)) {
		sdcmn_err7(("devpts_create_rvp: %s not valid pty"
		    "or not valid in this zone\n", nm));
		return (-1);
	}


	/*
	 * This is a valid pty (at least at this point in time).
	 * Create the node by setting the attribute. The rest
	 * is taken care of by devname_lookup_func().
	 */
	*vap = devpts_vattr;
	vap->va_rdev = makedevice(maj, min);
	vap->va_uid = uid;
	vap->va_gid = gid;
	gethrestime(&now);
	vap->va_atime = now;
	vap->va_mtime = now;
	vap->va_ctime = now;

	return (0);
}

/*
 * Clean pts sdev_nodes that are no longer valid.
 */
static void
devpts_prunedir(struct sdev_node *ddv)
{
	struct vnode *vp;
	struct sdev_node *dv, *next = NULL;
	int (*vtor)(struct sdev_node *) = NULL;

	ASSERT(ddv->sdev_flags & SDEV_VTOR);

	vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
	ASSERT(vtor);

	if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
		rw_exit(&ddv->sdev_contents);
		rw_enter(&ddv->sdev_contents, RW_WRITER);
	}

	for (dv = ddv->sdev_dot; dv; dv = next) {
		next = dv->sdev_next;

		/* skip stale nodes */
		if (dv->sdev_flags & SDEV_STALE)
			continue;

		/* validate and prune only ready nodes */
		if (dv->sdev_state != SDEV_READY)
			continue;

		switch (vtor(dv)) {
		case SDEV_VTOR_VALID:
		case SDEV_VTOR_SKIP:
			continue;
		case SDEV_VTOR_INVALID:
			sdcmn_err7(("prunedir: destroy invalid "
			    "node: %s(%p)\n", dv->sdev_name, (void *)dv));
			break;
		}
		vp = SDEVTOV(dv);
		if (vp->v_count > 0)
			continue;
		SDEV_HOLD(dv);
		/* remove the cache node */
		(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
		    SDEV_CACHE_DELETE);
	}
	rw_downgrade(&ddv->sdev_contents);
}

/*
 * Lookup for /dev/pts directory
 *	If the entry does not exist, the devpts_create_rvp() callback
 *	is invoked to create it. Nodes do not persist across reboot.
 *
 * There is a potential denial of service here via
 * fattach on top of a /dev/pts node - any permission changes
 * applied to the node, apply to the fattached file and not
 * to the underlying pts node. As a result when the previous
 * user fdetaches, the pts node is still owned by the previous
 * owner. To prevent this we don't allow fattach() on top of a pts
 * node. This is done by a modification in the namefs filesystem
 * where we check if the underlying node has the /dev/pts vnodeops.
 * We do this via VOP_REALVP() on the underlying specfs node.
 * sdev_nodes currently don't have a realvp. If a realvp is ever
 * created for sdev_nodes, then VOP_REALVP() will return the
 * actual realvp (possibly a ufs vnode). This will defeat the check
 * in namefs code which checks if VOP_REALVP() returns a devpts
 * node. We add an ASSERT here in /dev/pts lookup() to check for
 * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
 * change the code in the namefs filesystem code (in nm_mount()) to
 * access the realvp of the specfs node directly instead of using
 * VOP_REALVP().
 */
/*ARGSUSED3*/
static int
devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
    struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred)
{
	struct sdev_node *sdvp = VTOSDEV(dvp);
	struct sdev_node *dv;
	struct vnode *rvp = NULL;
	int error;

	error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
	    SDEV_VATTR);

	if (error == 0) {
		switch ((*vpp)->v_type) {
		case VCHR:
			dv = VTOSDEV(VTOS(*vpp)->s_realvp);
			ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp) == ENOSYS);
			break;
		case VDIR:
			dv = VTOSDEV(*vpp);
			break;
		default:
			cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
			    "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
			break;
		}
		ASSERT(SDEV_HELD(dv));
	}

	return (error);
}

/*
 * We allow create to find existing nodes
 *	- if the node doesn't exist - EROFS
 *	- creating an existing dir read-only succeeds, otherwise EISDIR
 *	- exclusive creates fail - EEXIST
 */
/*ARGSUSED2*/
static int
devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
    int mode, struct vnode **vpp, struct cred *cred, int flag)
{
	int error;
	struct vnode *vp;

	*vpp = NULL;

	error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred);
	if (error == 0) {
		if (excl == EXCL)
			error = EEXIST;
		else if (vp->v_type == VDIR && (mode & VWRITE))
			error = EISDIR;
		else
			error = VOP_ACCESS(vp, mode, 0, cred);

		if (error) {
			VN_RELE(vp);
		} else
			*vpp = vp;
	} else if (error == ENOENT) {
		error = EROFS;
	}

	return (error);
}

/*
 * Display all instantiated pts (slave) device nodes.
 * A /dev/pts entry will be created only after the first lookup of the slave
 * device succeeds.
 */
static int
devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
    int *eofp)
{
	struct sdev_node *sdvp = VTOSDEV(dvp);
	if (uiop->uio_offset == 0) {
		devpts_prunedir(sdvp);
	}

	return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
}


static int
devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
{
	ASSERT((protocol & AT_UID) || (protocol & AT_GID));
	ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
	    vap->va_uid, vap->va_gid);
	return (0);

}

/*ARGSUSED4*/
static int
devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
    struct cred *cred, caller_context_t *ctp)
{
	ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
	return (devname_setattr_func(vp, vap, flags, cred,
		    devpts_set_id, AT_UID|AT_GID));
}


/*
 * We override lookup and readdir to build entries based on the
 * in kernel pty table. Also override setattr/setsecattr to
 * avoid persisting permissions.
 */
const fs_operation_def_t devpts_vnodeops_tbl[] = {
	VOPNAME_READDIR,	{ .vop_readdir = devpts_readdir },
	VOPNAME_LOOKUP,		{ .vop_lookup = devpts_lookup },
	VOPNAME_CREATE,		{ .vop_create = devpts_create },
	VOPNAME_SETATTR,	{ .vop_setattr = devpts_setattr },
	VOPNAME_REMOVE,		{ .error = fs_nosys },
	VOPNAME_MKDIR,		{ .error = fs_nosys },
	VOPNAME_RMDIR,		{ .error = fs_nosys },
	VOPNAME_SYMLINK,	{ .error = fs_nosys },
	VOPNAME_SETSECATTR,	{ .error = fs_nosys },
	NULL,			NULL
};