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 /*
226260Sjg * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
232621Sllai1 * Use is subject to license terms.
242621Sllai1 */
252621Sllai1
262621Sllai1 /*
272621Sllai1 * vnode ops for the /dev/pts directory
282621Sllai1 * The lookup is based on the internal pty table. We also
292621Sllai1 * override readdir in order to delete pts nodes no longer
302621Sllai1 * in use.
312621Sllai1 */
322621Sllai1
332621Sllai1 #include <sys/types.h>
342621Sllai1 #include <sys/param.h>
352621Sllai1 #include <sys/sysmacros.h>
362621Sllai1 #include <sys/sunndi.h>
372621Sllai1 #include <fs/fs_subr.h>
382621Sllai1 #include <sys/fs/dv_node.h>
392621Sllai1 #include <sys/fs/sdev_impl.h>
402621Sllai1 #include <sys/policy.h>
412621Sllai1 #include <sys/ptms.h>
422621Sllai1 #include <sys/stat.h>
433898Srsb #include <sys/vfs_opreg.h>
442621Sllai1
452621Sllai1 #define DEVPTS_UID_DEFAULT 0
462621Sllai1 #define DEVPTS_GID_DEFAULT 3
472621Sllai1 #define DEVPTS_DEVMODE_DEFAULT (0620)
482621Sllai1
492621Sllai1 #define isdigit(ch) ((ch) >= '0' && (ch) <= '9')
502621Sllai1
512621Sllai1 static vattr_t devpts_vattr = {
522621Sllai1 AT_TYPE|AT_MODE|AT_UID|AT_GID, /* va_mask */
532621Sllai1 VCHR, /* va_type */
542621Sllai1 S_IFCHR | DEVPTS_DEVMODE_DEFAULT, /* va_mode */
552621Sllai1 DEVPTS_UID_DEFAULT, /* va_uid */
562621Sllai1 DEVPTS_GID_DEFAULT, /* va_gid */
572621Sllai1 0 /* 0 hereafter */
582621Sllai1 };
592621Sllai1
602621Sllai1 struct vnodeops *devpts_vnodeops;
612621Sllai1
622621Sllai1 struct vnodeops *
devpts_getvnodeops(void)632621Sllai1 devpts_getvnodeops(void)
642621Sllai1 {
652621Sllai1 return (devpts_vnodeops);
662621Sllai1 }
672621Sllai1
682621Sllai1 /*
692621Sllai1 * Convert string to minor number. Some care must be taken
702621Sllai1 * as we are processing user input. Catch cases like
712621Sllai1 * /dev/pts/4foo and /dev/pts/-1
722621Sllai1 */
732621Sllai1 static int
devpts_strtol(const char * nm,minor_t * mp)742621Sllai1 devpts_strtol(const char *nm, minor_t *mp)
752621Sllai1 {
762621Sllai1 long uminor = 0;
772621Sllai1 char *endptr = NULL;
782621Sllai1
792621Sllai1 if (nm == NULL || !isdigit(*nm))
802621Sllai1 return (EINVAL);
812621Sllai1
822621Sllai1 *mp = 0;
832621Sllai1 if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
842621Sllai1 *endptr != '\0' || uminor < 0) {
852621Sllai1 return (EINVAL);
862621Sllai1 }
872621Sllai1
883133Sjg *mp = (minor_t)uminor;
892621Sllai1 return (0);
902621Sllai1 }
912621Sllai1
922621Sllai1 /*
932621Sllai1 * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
942621Sllai1 * This serves two purposes
952621Sllai1 * - only valid pts nodes are returned during lookup() and readdir().
962621Sllai1 * - since pts sdev_nodes are not actively destroyed when a pty goes
972621Sllai1 * away, we use the validator to do deferred cleanup i.e. when such
982621Sllai1 * nodes are encountered during subsequent lookup() and readdir().
992621Sllai1 */
1002621Sllai1 /*ARGSUSED*/
1012621Sllai1 int
devpts_validate(struct sdev_node * dv)1022621Sllai1 devpts_validate(struct sdev_node *dv)
1032621Sllai1 {
1042621Sllai1 minor_t min;
1052621Sllai1 uid_t uid;
1062621Sllai1 gid_t gid;
1072621Sllai1 timestruc_t now;
1082621Sllai1 char *nm = dv->sdev_name;
1092621Sllai1
1102621Sllai1 ASSERT(!(dv->sdev_flags & SDEV_STALE));
1112621Sllai1 ASSERT(dv->sdev_state == SDEV_READY);
1122621Sllai1
1132621Sllai1 /* validate only READY nodes */
1142621Sllai1 if (dv->sdev_state != SDEV_READY) {
1152621Sllai1 sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
1162621Sllai1 nm, (void *)dv));
1172621Sllai1 return (SDEV_VTOR_SKIP);
1182621Sllai1 }
1192621Sllai1
1202621Sllai1 if (devpts_strtol(nm, &min) != 0) {
1212621Sllai1 sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
1222621Sllai1 return (SDEV_VTOR_INVALID);
1232621Sllai1 }
1242621Sllai1
1252621Sllai1 /*
1262621Sllai1 * Check if pts driver is attached
1272621Sllai1 */
1282621Sllai1 if (ptms_slave_attached() == (major_t)-1) {
1292621Sllai1 sdcmn_err7(("devpts_validate: slave not attached\n"));
1302621Sllai1 return (SDEV_VTOR_INVALID);
1312621Sllai1 }
1322621Sllai1
1332621Sllai1 if (ptms_minor_valid(min, &uid, &gid) == 0) {
1342621Sllai1 if (ptms_minor_exists(min)) {
1352621Sllai1 sdcmn_err7(("devpts_validate: valid in different zone "
1362621Sllai1 "%s\n", nm));
1372621Sllai1 return (SDEV_VTOR_SKIP);
1382621Sllai1 } else {
1392621Sllai1 sdcmn_err7(("devpts_validate: %s not valid pty\n",
1402621Sllai1 nm));
1412621Sllai1 return (SDEV_VTOR_INVALID);
1422621Sllai1 }
1432621Sllai1 }
1442621Sllai1
1452621Sllai1 ASSERT(dv->sdev_attr);
1462621Sllai1 if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
1472621Sllai1 dv->sdev_attr->va_uid = uid;
1482621Sllai1 dv->sdev_attr->va_gid = gid;
1492621Sllai1 gethrestime(&now);
1502621Sllai1 dv->sdev_attr->va_atime = now;
1512621Sllai1 dv->sdev_attr->va_mtime = now;
1522621Sllai1 dv->sdev_attr->va_ctime = now;
1532621Sllai1 sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
1542621Sllai1 }
1552621Sllai1
1562621Sllai1 return (SDEV_VTOR_VALID);
1572621Sllai1 }
1582621Sllai1
1592621Sllai1 /*
1602621Sllai1 * This callback is invoked from devname_lookup_func() to create
1612621Sllai1 * a pts entry when the node is not found in the cache.
1622621Sllai1 */
1632621Sllai1 /*ARGSUSED*/
1642621Sllai1 static int
devpts_create_rvp(struct sdev_node * ddv,char * nm,void ** arg,cred_t * cred,void * whatever,char * whichever)1652621Sllai1 devpts_create_rvp(struct sdev_node *ddv, char *nm,
1662621Sllai1 void **arg, cred_t *cred, void *whatever, char *whichever)
1672621Sllai1 {
1682621Sllai1 minor_t min;
1692621Sllai1 major_t maj;
1702621Sllai1 uid_t uid;
1712621Sllai1 gid_t gid;
1722621Sllai1 timestruc_t now;
1732621Sllai1 struct vattr *vap = (struct vattr *)arg;
1742621Sllai1
1752621Sllai1 if (devpts_strtol(nm, &min) != 0) {
1762621Sllai1 sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
1772621Sllai1 return (-1);
1782621Sllai1 }
1792621Sllai1
1802621Sllai1 /*
1812621Sllai1 * Check if pts driver is attached and if it is
1822621Sllai1 * get the major number.
1832621Sllai1 */
1842621Sllai1 maj = ptms_slave_attached();
1852621Sllai1 if (maj == (major_t)-1) {
1862621Sllai1 sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
1872621Sllai1 return (-1);
1882621Sllai1 }
1892621Sllai1
1902621Sllai1 /*
1912621Sllai1 * Only allow creation of ptys allocated to our zone
1922621Sllai1 */
1932621Sllai1 if (!ptms_minor_valid(min, &uid, &gid)) {
1942621Sllai1 sdcmn_err7(("devpts_create_rvp: %s not valid pty"
1952621Sllai1 "or not valid in this zone\n", nm));
1962621Sllai1 return (-1);
1972621Sllai1 }
1982621Sllai1
1992621Sllai1
2002621Sllai1 /*
2012621Sllai1 * This is a valid pty (at least at this point in time).
2022621Sllai1 * Create the node by setting the attribute. The rest
2032621Sllai1 * is taken care of by devname_lookup_func().
2042621Sllai1 */
2052621Sllai1 *vap = devpts_vattr;
2062621Sllai1 vap->va_rdev = makedevice(maj, min);
2072621Sllai1 vap->va_uid = uid;
2082621Sllai1 vap->va_gid = gid;
2092621Sllai1 gethrestime(&now);
2102621Sllai1 vap->va_atime = now;
2112621Sllai1 vap->va_mtime = now;
2122621Sllai1 vap->va_ctime = now;
2132621Sllai1
2142621Sllai1 return (0);
2152621Sllai1 }
2162621Sllai1
2172621Sllai1 /*
2182621Sllai1 * Clean pts sdev_nodes that are no longer valid.
2192621Sllai1 */
2202621Sllai1 static void
devpts_prunedir(struct sdev_node * ddv)2212621Sllai1 devpts_prunedir(struct sdev_node *ddv)
2222621Sllai1 {
2232621Sllai1 struct vnode *vp;
2242621Sllai1 struct sdev_node *dv, *next = NULL;
2252621Sllai1 int (*vtor)(struct sdev_node *) = NULL;
2262621Sllai1
2272621Sllai1 ASSERT(ddv->sdev_flags & SDEV_VTOR);
2282621Sllai1
2292621Sllai1 vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
2302621Sllai1 ASSERT(vtor);
2312621Sllai1
2322621Sllai1 if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
2332621Sllai1 rw_exit(&ddv->sdev_contents);
2342621Sllai1 rw_enter(&ddv->sdev_contents, RW_WRITER);
2352621Sllai1 }
2362621Sllai1
2376260Sjg for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
2386260Sjg next = SDEV_NEXT_ENTRY(ddv, dv);
2392621Sllai1
2402621Sllai1 /* validate and prune only ready nodes */
2412621Sllai1 if (dv->sdev_state != SDEV_READY)
2422621Sllai1 continue;
2432621Sllai1
2442621Sllai1 switch (vtor(dv)) {
2452621Sllai1 case SDEV_VTOR_VALID:
2462621Sllai1 case SDEV_VTOR_SKIP:
2472621Sllai1 continue;
2482621Sllai1 case SDEV_VTOR_INVALID:
249*8023SPhil.Kirk@Sun.COM case SDEV_VTOR_STALE:
2502621Sllai1 sdcmn_err7(("prunedir: destroy invalid "
2512621Sllai1 "node: %s(%p)\n", dv->sdev_name, (void *)dv));
2522621Sllai1 break;
2532621Sllai1 }
2542621Sllai1 vp = SDEVTOV(dv);
2552621Sllai1 if (vp->v_count > 0)
2562621Sllai1 continue;
2572621Sllai1 SDEV_HOLD(dv);
2582621Sllai1 /* remove the cache node */
2592621Sllai1 (void) sdev_cache_update(ddv, &dv, dv->sdev_name,
2602621Sllai1 SDEV_CACHE_DELETE);
2612621Sllai1 }
2622621Sllai1 rw_downgrade(&ddv->sdev_contents);
2632621Sllai1 }
2642621Sllai1
2652621Sllai1 /*
2662621Sllai1 * Lookup for /dev/pts directory
2672621Sllai1 * If the entry does not exist, the devpts_create_rvp() callback
2682621Sllai1 * is invoked to create it. Nodes do not persist across reboot.
2693442Svikram *
2703442Svikram * There is a potential denial of service here via
2713442Svikram * fattach on top of a /dev/pts node - any permission changes
2723442Svikram * applied to the node, apply to the fattached file and not
2733442Svikram * to the underlying pts node. As a result when the previous
2743442Svikram * user fdetaches, the pts node is still owned by the previous
2753442Svikram * owner. To prevent this we don't allow fattach() on top of a pts
2763442Svikram * node. This is done by a modification in the namefs filesystem
2773442Svikram * where we check if the underlying node has the /dev/pts vnodeops.
2783442Svikram * We do this via VOP_REALVP() on the underlying specfs node.
2793442Svikram * sdev_nodes currently don't have a realvp. If a realvp is ever
2803442Svikram * created for sdev_nodes, then VOP_REALVP() will return the
2813442Svikram * actual realvp (possibly a ufs vnode). This will defeat the check
2823442Svikram * in namefs code which checks if VOP_REALVP() returns a devpts
2833442Svikram * node. We add an ASSERT here in /dev/pts lookup() to check for
2843442Svikram * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
2853442Svikram * change the code in the namefs filesystem code (in nm_mount()) to
2863442Svikram * access the realvp of the specfs node directly instead of using
2873442Svikram * VOP_REALVP().
2882621Sllai1 */
2892621Sllai1 /*ARGSUSED3*/
2902621Sllai1 static int
devpts_lookup(struct vnode * dvp,char * nm,struct vnode ** vpp,struct pathname * pnp,int flags,struct vnode * rdir,struct cred * cred,caller_context_t * ct,int * direntflags,pathname_t * realpnp)2912621Sllai1 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
2925331Samw struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
2935331Samw caller_context_t *ct, int *direntflags, pathname_t *realpnp)
2942621Sllai1 {
2952621Sllai1 struct sdev_node *sdvp = VTOSDEV(dvp);
2962621Sllai1 struct sdev_node *dv;
2973442Svikram struct vnode *rvp = NULL;
2982621Sllai1 int error;
2992621Sllai1
3002621Sllai1 error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
3012621Sllai1 SDEV_VATTR);
3022621Sllai1
3032621Sllai1 if (error == 0) {
3042621Sllai1 switch ((*vpp)->v_type) {
3052621Sllai1 case VCHR:
3062621Sllai1 dv = VTOSDEV(VTOS(*vpp)->s_realvp);
3075331Samw ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
3082621Sllai1 break;
3092621Sllai1 case VDIR:
3102621Sllai1 dv = VTOSDEV(*vpp);
3112621Sllai1 break;
3122621Sllai1 default:
3132621Sllai1 cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
3142621Sllai1 "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
3152621Sllai1 break;
3162621Sllai1 }
3172621Sllai1 ASSERT(SDEV_HELD(dv));
3182621Sllai1 }
3192621Sllai1
3202621Sllai1 return (error);
3212621Sllai1 }
3222621Sllai1
3232621Sllai1 /*
3242621Sllai1 * We allow create to find existing nodes
3252621Sllai1 * - if the node doesn't exist - EROFS
3262621Sllai1 * - creating an existing dir read-only succeeds, otherwise EISDIR
3272621Sllai1 * - exclusive creates fail - EEXIST
3282621Sllai1 */
3292621Sllai1 /*ARGSUSED2*/
3302621Sllai1 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,caller_context_t * ct,vsecattr_t * vsecp)3312621Sllai1 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
3325331Samw int mode, struct vnode **vpp, struct cred *cred, int flag,
3335331Samw caller_context_t *ct, vsecattr_t *vsecp)
3342621Sllai1 {
3352621Sllai1 int error;
3362621Sllai1 struct vnode *vp;
3372621Sllai1
3382621Sllai1 *vpp = NULL;
3392621Sllai1
3405331Samw error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL,
3415331Samw NULL);
3422621Sllai1 if (error == 0) {
3432621Sllai1 if (excl == EXCL)
3442621Sllai1 error = EEXIST;
3452621Sllai1 else if (vp->v_type == VDIR && (mode & VWRITE))
3462621Sllai1 error = EISDIR;
3472621Sllai1 else
3485331Samw error = VOP_ACCESS(vp, mode, 0, cred, ct);
3492621Sllai1
3502621Sllai1 if (error) {
3512621Sllai1 VN_RELE(vp);
3522621Sllai1 } else
3532621Sllai1 *vpp = vp;
3542621Sllai1 } else if (error == ENOENT) {
3552621Sllai1 error = EROFS;
3562621Sllai1 }
3572621Sllai1
3582621Sllai1 return (error);
3592621Sllai1 }
3602621Sllai1
3612621Sllai1 /*
3622621Sllai1 * Display all instantiated pts (slave) device nodes.
3632621Sllai1 * A /dev/pts entry will be created only after the first lookup of the slave
3642621Sllai1 * device succeeds.
3652621Sllai1 */
3665331Samw /*ARGSUSED4*/
3672621Sllai1 static int
devpts_readdir(struct vnode * dvp,struct uio * uiop,struct cred * cred,int * eofp,caller_context_t * ct,int flags)3682621Sllai1 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
3695331Samw int *eofp, caller_context_t *ct, int flags)
3702621Sllai1 {
3712621Sllai1 struct sdev_node *sdvp = VTOSDEV(dvp);
3722621Sllai1 if (uiop->uio_offset == 0) {
3732621Sllai1 devpts_prunedir(sdvp);
3742621Sllai1 }
3752621Sllai1
3762621Sllai1 return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
3772621Sllai1 }
3782621Sllai1
3792621Sllai1
3802621Sllai1 static int
devpts_set_id(struct sdev_node * dv,struct vattr * vap,int protocol)3812621Sllai1 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
3822621Sllai1 {
3832621Sllai1 ASSERT((protocol & AT_UID) || (protocol & AT_GID));
3842621Sllai1 ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
3852621Sllai1 vap->va_uid, vap->va_gid);
3862621Sllai1 return (0);
3872621Sllai1
3882621Sllai1 }
3892621Sllai1
3903748Sjg /*ARGSUSED4*/
3912621Sllai1 static int
devpts_setattr(struct vnode * vp,struct vattr * vap,int flags,struct cred * cred,caller_context_t * ctp)3922621Sllai1 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
3933748Sjg struct cred *cred, caller_context_t *ctp)
3942621Sllai1 {
3952621Sllai1 ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
3962621Sllai1 return (devname_setattr_func(vp, vap, flags, cred,
3976260Sjg devpts_set_id, AT_UID|AT_GID));
3982621Sllai1 }
3992621Sllai1
4003442Svikram
4012621Sllai1 /*
4022621Sllai1 * We override lookup and readdir to build entries based on the
4032621Sllai1 * in kernel pty table. Also override setattr/setsecattr to
4042621Sllai1 * avoid persisting permissions.
4052621Sllai1 */
4062621Sllai1 const fs_operation_def_t devpts_vnodeops_tbl[] = {
4073898Srsb VOPNAME_READDIR, { .vop_readdir = devpts_readdir },
4083898Srsb VOPNAME_LOOKUP, { .vop_lookup = devpts_lookup },
4093898Srsb VOPNAME_CREATE, { .vop_create = devpts_create },
4103898Srsb VOPNAME_SETATTR, { .vop_setattr = devpts_setattr },
4113898Srsb VOPNAME_REMOVE, { .error = fs_nosys },
4123898Srsb VOPNAME_MKDIR, { .error = fs_nosys },
4133898Srsb VOPNAME_RMDIR, { .error = fs_nosys },
4143898Srsb VOPNAME_SYMLINK, { .error = fs_nosys },
4153898Srsb VOPNAME_SETSECATTR, { .error = fs_nosys },
4163898Srsb NULL, NULL
4172621Sllai1 };
418