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> 453898Srsb #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 dv->sdev_attr->va_uid = uid; 1502621Sllai1 dv->sdev_attr->va_gid = gid; 1512621Sllai1 gethrestime(&now); 1522621Sllai1 dv->sdev_attr->va_atime = now; 1532621Sllai1 dv->sdev_attr->va_mtime = now; 1542621Sllai1 dv->sdev_attr->va_ctime = now; 1552621Sllai1 sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm)); 1562621Sllai1 } 1572621Sllai1 1582621Sllai1 return (SDEV_VTOR_VALID); 1592621Sllai1 } 1602621Sllai1 1612621Sllai1 /* 1622621Sllai1 * This callback is invoked from devname_lookup_func() to create 1632621Sllai1 * a pts entry when the node is not found in the cache. 1642621Sllai1 */ 1652621Sllai1 /*ARGSUSED*/ 1662621Sllai1 static int 1672621Sllai1 devpts_create_rvp(struct sdev_node *ddv, char *nm, 1682621Sllai1 void **arg, cred_t *cred, void *whatever, char *whichever) 1692621Sllai1 { 1702621Sllai1 minor_t min; 1712621Sllai1 major_t maj; 1722621Sllai1 uid_t uid; 1732621Sllai1 gid_t gid; 1742621Sllai1 timestruc_t now; 1752621Sllai1 struct vattr *vap = (struct vattr *)arg; 1762621Sllai1 1772621Sllai1 if (devpts_strtol(nm, &min) != 0) { 1782621Sllai1 sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm)); 1792621Sllai1 return (-1); 1802621Sllai1 } 1812621Sllai1 1822621Sllai1 /* 1832621Sllai1 * Check if pts driver is attached and if it is 1842621Sllai1 * get the major number. 1852621Sllai1 */ 1862621Sllai1 maj = ptms_slave_attached(); 1872621Sllai1 if (maj == (major_t)-1) { 1882621Sllai1 sdcmn_err7(("devpts_create_rvp: slave not attached\n")); 1892621Sllai1 return (-1); 1902621Sllai1 } 1912621Sllai1 1922621Sllai1 /* 1932621Sllai1 * Only allow creation of ptys allocated to our zone 1942621Sllai1 */ 1952621Sllai1 if (!ptms_minor_valid(min, &uid, &gid)) { 1962621Sllai1 sdcmn_err7(("devpts_create_rvp: %s not valid pty" 1972621Sllai1 "or not valid in this zone\n", nm)); 1982621Sllai1 return (-1); 1992621Sllai1 } 2002621Sllai1 2012621Sllai1 2022621Sllai1 /* 2032621Sllai1 * This is a valid pty (at least at this point in time). 2042621Sllai1 * Create the node by setting the attribute. The rest 2052621Sllai1 * is taken care of by devname_lookup_func(). 2062621Sllai1 */ 2072621Sllai1 *vap = devpts_vattr; 2082621Sllai1 vap->va_rdev = makedevice(maj, min); 2092621Sllai1 vap->va_uid = uid; 2102621Sllai1 vap->va_gid = gid; 2112621Sllai1 gethrestime(&now); 2122621Sllai1 vap->va_atime = now; 2132621Sllai1 vap->va_mtime = now; 2142621Sllai1 vap->va_ctime = now; 2152621Sllai1 2162621Sllai1 return (0); 2172621Sllai1 } 2182621Sllai1 2192621Sllai1 /* 2202621Sllai1 * Clean pts sdev_nodes that are no longer valid. 2212621Sllai1 */ 2222621Sllai1 static void 2232621Sllai1 devpts_prunedir(struct sdev_node *ddv) 2242621Sllai1 { 2252621Sllai1 struct vnode *vp; 2262621Sllai1 struct sdev_node *dv, *next = NULL; 2272621Sllai1 int (*vtor)(struct sdev_node *) = NULL; 2282621Sllai1 2292621Sllai1 ASSERT(ddv->sdev_flags & SDEV_VTOR); 2302621Sllai1 2312621Sllai1 vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv); 2322621Sllai1 ASSERT(vtor); 2332621Sllai1 2342621Sllai1 if (rw_tryupgrade(&ddv->sdev_contents) == NULL) { 2352621Sllai1 rw_exit(&ddv->sdev_contents); 2362621Sllai1 rw_enter(&ddv->sdev_contents, RW_WRITER); 2372621Sllai1 } 2382621Sllai1 2392621Sllai1 for (dv = ddv->sdev_dot; dv; dv = next) { 2402621Sllai1 next = dv->sdev_next; 2412621Sllai1 2422621Sllai1 /* skip stale nodes */ 2432621Sllai1 if (dv->sdev_flags & SDEV_STALE) 2442621Sllai1 continue; 2452621Sllai1 2462621Sllai1 /* validate and prune only ready nodes */ 2472621Sllai1 if (dv->sdev_state != SDEV_READY) 2482621Sllai1 continue; 2492621Sllai1 2502621Sllai1 switch (vtor(dv)) { 2512621Sllai1 case SDEV_VTOR_VALID: 2522621Sllai1 case SDEV_VTOR_SKIP: 2532621Sllai1 continue; 2542621Sllai1 case SDEV_VTOR_INVALID: 2552621Sllai1 sdcmn_err7(("prunedir: destroy invalid " 2562621Sllai1 "node: %s(%p)\n", dv->sdev_name, (void *)dv)); 2572621Sllai1 break; 2582621Sllai1 } 2592621Sllai1 vp = SDEVTOV(dv); 2602621Sllai1 if (vp->v_count > 0) 2612621Sllai1 continue; 2622621Sllai1 SDEV_HOLD(dv); 2632621Sllai1 /* remove the cache node */ 2642621Sllai1 (void) sdev_cache_update(ddv, &dv, dv->sdev_name, 2652621Sllai1 SDEV_CACHE_DELETE); 2662621Sllai1 } 2672621Sllai1 rw_downgrade(&ddv->sdev_contents); 2682621Sllai1 } 2692621Sllai1 2702621Sllai1 /* 2712621Sllai1 * Lookup for /dev/pts directory 2722621Sllai1 * If the entry does not exist, the devpts_create_rvp() callback 2732621Sllai1 * is invoked to create it. Nodes do not persist across reboot. 2743442Svikram * 2753442Svikram * There is a potential denial of service here via 2763442Svikram * fattach on top of a /dev/pts node - any permission changes 2773442Svikram * applied to the node, apply to the fattached file and not 2783442Svikram * to the underlying pts node. As a result when the previous 2793442Svikram * user fdetaches, the pts node is still owned by the previous 2803442Svikram * owner. To prevent this we don't allow fattach() on top of a pts 2813442Svikram * node. This is done by a modification in the namefs filesystem 2823442Svikram * where we check if the underlying node has the /dev/pts vnodeops. 2833442Svikram * We do this via VOP_REALVP() on the underlying specfs node. 2843442Svikram * sdev_nodes currently don't have a realvp. If a realvp is ever 2853442Svikram * created for sdev_nodes, then VOP_REALVP() will return the 2863442Svikram * actual realvp (possibly a ufs vnode). This will defeat the check 2873442Svikram * in namefs code which checks if VOP_REALVP() returns a devpts 2883442Svikram * node. We add an ASSERT here in /dev/pts lookup() to check for 2893442Svikram * this condition. If sdev_nodes ever get a VOP_REALVP() entry point, 2903442Svikram * change the code in the namefs filesystem code (in nm_mount()) to 2913442Svikram * access the realvp of the specfs node directly instead of using 2923442Svikram * VOP_REALVP(). 2932621Sllai1 */ 2942621Sllai1 /*ARGSUSED3*/ 2952621Sllai1 static int 2962621Sllai1 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp, 297*5331Samw struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred, 298*5331Samw caller_context_t *ct, int *direntflags, pathname_t *realpnp) 2992621Sllai1 { 3002621Sllai1 struct sdev_node *sdvp = VTOSDEV(dvp); 3012621Sllai1 struct sdev_node *dv; 3023442Svikram struct vnode *rvp = NULL; 3032621Sllai1 int error; 3042621Sllai1 3052621Sllai1 error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp, 3062621Sllai1 SDEV_VATTR); 3072621Sllai1 3082621Sllai1 if (error == 0) { 3092621Sllai1 switch ((*vpp)->v_type) { 3102621Sllai1 case VCHR: 3112621Sllai1 dv = VTOSDEV(VTOS(*vpp)->s_realvp); 312*5331Samw ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS); 3132621Sllai1 break; 3142621Sllai1 case VDIR: 3152621Sllai1 dv = VTOSDEV(*vpp); 3162621Sllai1 break; 3172621Sllai1 default: 3182621Sllai1 cmn_err(CE_PANIC, "devpts_lookup: Unsupported node " 3192621Sllai1 "type: %p: %d", (void *)(*vpp), (*vpp)->v_type); 3202621Sllai1 break; 3212621Sllai1 } 3222621Sllai1 ASSERT(SDEV_HELD(dv)); 3232621Sllai1 } 3242621Sllai1 3252621Sllai1 return (error); 3262621Sllai1 } 3272621Sllai1 3282621Sllai1 /* 3292621Sllai1 * We allow create to find existing nodes 3302621Sllai1 * - if the node doesn't exist - EROFS 3312621Sllai1 * - creating an existing dir read-only succeeds, otherwise EISDIR 3322621Sllai1 * - exclusive creates fail - EEXIST 3332621Sllai1 */ 3342621Sllai1 /*ARGSUSED2*/ 3352621Sllai1 static int 3362621Sllai1 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl, 337*5331Samw int mode, struct vnode **vpp, struct cred *cred, int flag, 338*5331Samw caller_context_t *ct, vsecattr_t *vsecp) 3392621Sllai1 { 3402621Sllai1 int error; 3412621Sllai1 struct vnode *vp; 3422621Sllai1 3432621Sllai1 *vpp = NULL; 3442621Sllai1 345*5331Samw error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL, 346*5331Samw NULL); 3472621Sllai1 if (error == 0) { 3482621Sllai1 if (excl == EXCL) 3492621Sllai1 error = EEXIST; 3502621Sllai1 else if (vp->v_type == VDIR && (mode & VWRITE)) 3512621Sllai1 error = EISDIR; 3522621Sllai1 else 353*5331Samw error = VOP_ACCESS(vp, mode, 0, cred, ct); 3542621Sllai1 3552621Sllai1 if (error) { 3562621Sllai1 VN_RELE(vp); 3572621Sllai1 } else 3582621Sllai1 *vpp = vp; 3592621Sllai1 } else if (error == ENOENT) { 3602621Sllai1 error = EROFS; 3612621Sllai1 } 3622621Sllai1 3632621Sllai1 return (error); 3642621Sllai1 } 3652621Sllai1 3662621Sllai1 /* 3672621Sllai1 * Display all instantiated pts (slave) device nodes. 3682621Sllai1 * A /dev/pts entry will be created only after the first lookup of the slave 3692621Sllai1 * device succeeds. 3702621Sllai1 */ 371*5331Samw /*ARGSUSED4*/ 3722621Sllai1 static int 3732621Sllai1 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred, 374*5331Samw int *eofp, caller_context_t *ct, int flags) 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[] = { 4123898Srsb VOPNAME_READDIR, { .vop_readdir = devpts_readdir }, 4133898Srsb VOPNAME_LOOKUP, { .vop_lookup = devpts_lookup }, 4143898Srsb VOPNAME_CREATE, { .vop_create = devpts_create }, 4153898Srsb VOPNAME_SETATTR, { .vop_setattr = devpts_setattr }, 4163898Srsb VOPNAME_REMOVE, { .error = fs_nosys }, 4173898Srsb VOPNAME_MKDIR, { .error = fs_nosys }, 4183898Srsb VOPNAME_RMDIR, { .error = fs_nosys }, 4193898Srsb VOPNAME_SYMLINK, { .error = fs_nosys }, 4203898Srsb VOPNAME_SETSECATTR, { .error = fs_nosys }, 4213898Srsb NULL, NULL 4222621Sllai1 }; 423