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 int
 101 devpts_validate(struct sdev_node *dv)
 102 {
 103         minor_t min;
 104         uid_t uid;
 105         gid_t gid;
 106         timestruc_t now;
 107         char *nm = dv->sdev_name;
 108 
 109         ASSERT(dv->sdev_state == SDEV_READY);
 110 
 111         /* validate only READY nodes */
 112         if (dv->sdev_state != SDEV_READY) {
 113                 sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
 114                     nm, (void *)dv));
 115                 return (SDEV_VTOR_SKIP);
 116         }
 117 
 118         if (devpts_strtol(nm, &min) != 0) {
 119                 sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
 120                 return (SDEV_VTOR_INVALID);
 121         }
 122 
 123         /*
 124          * Check if pts driver is attached
 125          */
 126         if (ptms_subsidiary_attached() == (major_t)-1) {
 127                 sdcmn_err7(("devpts_validate: subsidiary not attached\n"));
 128                 return (SDEV_VTOR_INVALID);
 129         }
 130 
 131         if (ptms_minor_valid(min, &uid, &gid) == 0) {
 132                 if (ptms_minor_exists(min)) {
 133                         sdcmn_err7(("devpts_validate: valid in different zone "
 134                             "%s\n", nm));
 135                         return (SDEV_VTOR_SKIP);
 136                 } else {
 137                         sdcmn_err7(("devpts_validate: %s not valid pty\n",
 138                             nm));
 139                         return (SDEV_VTOR_INVALID);
 140                 }
 141         }
 142 
 143         ASSERT(dv->sdev_attr);
 144         if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
 145                 dv->sdev_attr->va_uid = uid;
 146                 dv->sdev_attr->va_gid = gid;
 147                 gethrestime(&now);
 148                 dv->sdev_attr->va_atime = now;
 149                 dv->sdev_attr->va_mtime = now;
 150                 dv->sdev_attr->va_ctime = now;
 151                 sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
 152         }
 153 
 154         return (SDEV_VTOR_VALID);
 155 }
 156 
 157 /*
 158  * This callback is invoked from devname_lookup_func() to create
 159  * a pts entry when the node is not found in the cache.
 160  */
 161 static int
 162 devpts_create_rvp(struct sdev_node *ddv, char *nm,
 163     void **arg, cred_t *cred, void *whatever, char *whichever)
 164 {
 165         minor_t min;
 166         major_t maj;
 167         uid_t uid;
 168         gid_t gid;
 169         timestruc_t now;
 170         struct vattr *vap = (struct vattr *)arg;
 171 
 172         if (devpts_strtol(nm, &min) != 0) {
 173                 sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
 174                 return (-1);
 175         }
 176 
 177         /*
 178          * Check if pts driver is attached and if it is get the major number.
 179          */
 180         maj = ptms_subsidiary_attached();
 181         if (maj == (major_t)-1) {
 182                 sdcmn_err7(("devpts_create_rvp: subsidiary not attached\n"));
 183                 return (-1);
 184         }
 185 
 186         /*
 187          * Only allow creation of ptys allocated to our zone
 188          */
 189         if (!ptms_minor_valid(min, &uid, &gid)) {
 190                 sdcmn_err7(("devpts_create_rvp: %s not valid pty"
 191                     "or not valid in this zone\n", nm));
 192                 return (-1);
 193         }
 194 
 195 
 196         /*
 197          * This is a valid pty (at least at this point in time).
 198          * Create the node by setting the attribute. The rest
 199          * is taken care of by devname_lookup_func().
 200          */
 201         *vap = devpts_vattr;
 202         vap->va_rdev = makedevice(maj, min);
 203         vap->va_uid = uid;
 204         vap->va_gid = gid;
 205         gethrestime(&now);
 206         vap->va_atime = now;
 207         vap->va_mtime = now;
 208         vap->va_ctime = now;
 209 
 210         return (0);
 211 }
 212 
 213 /*
 214  * Clean pts sdev_nodes that are no longer valid.
 215  */
 216 static void
 217 devpts_prunedir(struct sdev_node *ddv)
 218 {
 219         struct vnode *vp;
 220         struct sdev_node *dv, *next = NULL;
 221         int (*vtor)(struct sdev_node *) = NULL;
 222 
 223         ASSERT(ddv->sdev_flags & SDEV_VTOR);
 224 
 225         vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
 226         ASSERT(vtor);
 227 
 228         if (rw_tryupgrade(&ddv->sdev_contents) == 0) {
 229                 rw_exit(&ddv->sdev_contents);
 230                 rw_enter(&ddv->sdev_contents, RW_WRITER);
 231         }
 232 
 233         for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
 234                 next = SDEV_NEXT_ENTRY(ddv, dv);
 235 
 236                 /* validate and prune only ready nodes */
 237                 if (dv->sdev_state != SDEV_READY)
 238                         continue;
 239 
 240                 switch (vtor(dv)) {
 241                 case SDEV_VTOR_VALID:
 242                 case SDEV_VTOR_SKIP:
 243                         continue;
 244                 case SDEV_VTOR_INVALID:
 245                 case SDEV_VTOR_STALE:
 246                         sdcmn_err7(("prunedir: destroy invalid "
 247                             "node: %s(%p)\n", dv->sdev_name, (void *)dv));
 248                         break;
 249                 }
 250                 vp = SDEVTOV(dv);
 251                 if (vp->v_count > 0)
 252                         continue;
 253                 SDEV_HOLD(dv);
 254                 /* remove the cache node */
 255                 (void) sdev_cache_update(ddv, &dv, dv->sdev_name,
 256                     SDEV_CACHE_DELETE);
 257                 SDEV_RELE(dv);
 258         }
 259         rw_downgrade(&ddv->sdev_contents);
 260 }
 261 
 262 /*
 263  * Lookup for /dev/pts directory
 264  *      If the entry does not exist, the devpts_create_rvp() callback
 265  *      is invoked to create it. Nodes do not persist across reboot.
 266  *
 267  * There is a potential denial of service here via
 268  * fattach on top of a /dev/pts node - any permission changes
 269  * applied to the node, apply to the fattached file and not
 270  * to the underlying pts node. As a result when the previous
 271  * user fdetaches, the pts node is still owned by the previous
 272  * owner. To prevent this we don't allow fattach() on top of a pts
 273  * node. This is done by a modification in the namefs filesystem
 274  * where we check if the underlying node has the /dev/pts vnodeops.
 275  * We do this via VOP_REALVP() on the underlying specfs node.
 276  * sdev_nodes currently don't have a realvp. If a realvp is ever
 277  * created for sdev_nodes, then VOP_REALVP() will return the
 278  * actual realvp (possibly a ufs vnode). This will defeat the check
 279  * in namefs code which checks if VOP_REALVP() returns a devpts
 280  * node. We add an ASSERT here in /dev/pts lookup() to check for
 281  * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
 282  * change the code in the namefs filesystem code (in nm_mount()) to
 283  * access the realvp of the specfs node directly instead of using
 284  * VOP_REALVP().
 285  */
 286 static int
 287 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
 288     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
 289     caller_context_t *ct, int *direntflags, pathname_t *realpnp)
 290 {
 291         struct sdev_node *sdvp = VTOSDEV(dvp);
 292         struct sdev_node *dv;
 293         struct vnode *rvp = NULL;
 294         int error;
 295 
 296         error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
 297             SDEV_VATTR);
 298 
 299         if (error == 0) {
 300                 switch ((*vpp)->v_type) {
 301                 case VCHR:
 302                         dv = VTOSDEV(VTOS(*vpp)->s_realvp);
 303                         ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
 304                         break;
 305                 case VDIR:
 306                         dv = VTOSDEV(*vpp);
 307                         break;
 308                 default:
 309                         cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
 310                             "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
 311                         break;
 312                 }
 313                 ASSERT(SDEV_HELD(dv));
 314         }
 315 
 316         return (error);
 317 }
 318 
 319 /*
 320  * We allow create to find existing nodes
 321  *      - if the node doesn't exist - EROFS
 322  *      - creating an existing dir read-only succeeds, otherwise EISDIR
 323  *      - exclusive creates fail - EEXIST
 324  */
 325 static int
 326 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
 327     int mode, struct vnode **vpp, struct cred *cred, int flag,
 328     caller_context_t *ct, vsecattr_t *vsecp)
 329 {
 330         int error;
 331         struct vnode *vp;
 332 
 333         *vpp = NULL;
 334 
 335         error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL,
 336             NULL);
 337         if (error == 0) {
 338                 if (excl == EXCL)
 339                         error = EEXIST;
 340                 else if (vp->v_type == VDIR && (mode & VWRITE))
 341                         error = EISDIR;
 342                 else
 343                         error = VOP_ACCESS(vp, mode, 0, cred, ct);
 344 
 345                 if (error) {
 346                         VN_RELE(vp);
 347                 } else
 348                         *vpp = vp;
 349         } else if (error == ENOENT) {
 350                 error = EROFS;
 351         }
 352 
 353         return (error);
 354 }
 355 
 356 /*
 357  * Display all instantiated pts (subsidiary) device nodes.
 358  * A /dev/pts entry will be created only after the first lookup of the
 359  * subsidiary device succeeds.
 360  */
 361 static int
 362 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
 363     int *eofp, caller_context_t *ct, int flags)
 364 {
 365         struct sdev_node *sdvp = VTOSDEV(dvp);
 366         if (uiop->uio_offset == 0) {
 367                 devpts_prunedir(sdvp);
 368         }
 369 
 370         return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
 371 }
 372 
 373 
 374 static int
 375 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
 376 {
 377         ASSERT((protocol & AT_UID) || (protocol & AT_GID));
 378         ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
 379             vap->va_uid, vap->va_gid);
 380         return (0);
 381 
 382 }
 383 
 384 static int
 385 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
 386     struct cred *cred, caller_context_t *ctp)
 387 {
 388         ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
 389         return (devname_setattr_func(vp, vap, flags, cred,
 390             devpts_set_id, AT_UID|AT_GID));
 391 }
 392 
 393 
 394 /*
 395  * We override lookup and readdir to build entries based on the
 396  * in kernel pty table. Also override setattr/setsecattr to
 397  * avoid persisting permissions.
 398  */
 399 const fs_operation_def_t devpts_vnodeops_tbl[] = {
 400         VOPNAME_READDIR,        { .vop_readdir = devpts_readdir },
 401         VOPNAME_LOOKUP,         { .vop_lookup = devpts_lookup },
 402         VOPNAME_CREATE,         { .vop_create = devpts_create },
 403         VOPNAME_SETATTR,        { .vop_setattr = devpts_setattr },
 404         VOPNAME_REMOVE,         { .error = fs_nosys },
 405         VOPNAME_MKDIR,          { .error = fs_nosys },
 406         VOPNAME_RMDIR,          { .error = fs_nosys },
 407         VOPNAME_SYMLINK,        { .error = fs_nosys },
 408         VOPNAME_SETSECATTR,     { .error = fs_nosys },
 409         NULL,                   NULL
 410 };