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_state == SDEV_READY);
 111 
 112         /* validate only READY nodes */
 113         if (dv->sdev_state != SDEV_READY) {
 114                 sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
 115                     nm, (void *)dv));
 116                 return (SDEV_VTOR_SKIP);
 117         }
 118 
 119         if (devpts_strtol(nm, &min) != 0) {
 120                 sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
 121                 return (SDEV_VTOR_INVALID);
 122         }
 123 
 124         /*
 125          * Check if pts driver is attached
 126          */
 127         if (ptms_slave_attached() == (major_t)-1) {
 128                 sdcmn_err7(("devpts_validate: slave not attached\n"));
 129                 return (SDEV_VTOR_INVALID);
 130         }
 131 
 132         if (ptms_minor_valid(min, &uid, &gid) == 0) {
 133                 if (ptms_minor_exists(min)) {
 134                         sdcmn_err7(("devpts_validate: valid in different zone "
 135                             "%s\n", nm));
 136                         return (SDEV_VTOR_SKIP);
 137                 } else {
 138                         sdcmn_err7(("devpts_validate: %s not valid pty\n",
 139                             nm));
 140                         return (SDEV_VTOR_INVALID);
 141                 }
 142         }
 143 
 144         ASSERT(dv->sdev_attr);
 145         if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
 146                 dv->sdev_attr->va_uid = uid;
 147                 dv->sdev_attr->va_gid = gid;
 148                 gethrestime(&now);
 149                 dv->sdev_attr->va_atime = now;
 150                 dv->sdev_attr->va_mtime = now;
 151                 dv->sdev_attr->va_ctime = now;
 152                 sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
 153         }
 154 
 155         return (SDEV_VTOR_VALID);
 156 }
 157 
 158 /*
 159  * This callback is invoked from devname_lookup_func() to create
 160  * a pts entry when the node is not found in the cache.
 161  */
 162 /*ARGSUSED*/
 163 static int
 164 devpts_create_rvp(struct sdev_node *ddv, char *nm,
 165     void **arg, cred_t *cred, void *whatever, char *whichever)
 166 {
 167         minor_t min;
 168         major_t maj;
 169         uid_t uid;
 170         gid_t gid;
 171         timestruc_t now;
 172         struct vattr *vap = (struct vattr *)arg;
 173 
 174         if (devpts_strtol(nm, &min) != 0) {
 175                 sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
 176                 return (-1);
 177         }
 178 
 179         /*
 180          * Check if pts driver is attached and if it is
 181          * get the major number.
 182          */
 183         maj = ptms_slave_attached();
 184         if (maj == (major_t)-1) {
 185                 sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
 186                 return (-1);
 187         }
 188 
 189         /*
 190          * Only allow creation of ptys allocated to our zone
 191          */
 192         if (!ptms_minor_valid(min, &uid, &gid)) {
 193                 sdcmn_err7(("devpts_create_rvp: %s not valid pty"
 194                     "or not valid in this zone\n", nm));
 195                 return (-1);
 196         }
 197 
 198 
 199         /*
 200          * This is a valid pty (at least at this point in time).
 201          * Create the node by setting the attribute. The rest
 202          * is taken care of by devname_lookup_func().
 203          */
 204         *vap = devpts_vattr;
 205         vap->va_rdev = makedevice(maj, min);
 206         vap->va_uid = uid;
 207         vap->va_gid = gid;
 208         gethrestime(&now);
 209         vap->va_atime = now;
 210         vap->va_mtime = now;
 211         vap->va_ctime = now;
 212 
 213         return (0);
 214 }
 215 
 216 /*
 217  * Clean pts sdev_nodes that are no longer valid.
 218  */
 219 static void
 220 devpts_prunedir(struct sdev_node *ddv)
 221 {
 222         struct vnode *vp;
 223         struct sdev_node *dv, *next = NULL;
 224         int (*vtor)(struct sdev_node *) = NULL;
 225 
 226         ASSERT(ddv->sdev_flags & SDEV_VTOR);
 227 
 228         vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
 229         ASSERT(vtor);
 230 
 231         if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
 232                 rw_exit(&ddv->sdev_contents);
 233                 rw_enter(&ddv->sdev_contents, RW_WRITER);
 234         }
 235 
 236         for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
 237                 next = SDEV_NEXT_ENTRY(ddv, dv);
 238 
 239                 /* validate and prune only ready nodes */
 240                 if (dv->sdev_state != SDEV_READY)
 241                         continue;
 242 
 243                 switch (vtor(dv)) {
 244                 case SDEV_VTOR_VALID:
 245                 case SDEV_VTOR_SKIP:
 246                         continue;
 247                 case SDEV_VTOR_INVALID:
 248                 case SDEV_VTOR_STALE:
 249                         sdcmn_err7(("prunedir: destroy invalid "
 250                             "node: %s(%p)\n", dv->sdev_name, (void *)dv));
 251                         break;
 252                 }
 253                 vp = SDEVTOV(dv);
 254                 if (vp->v_count > 0)
 255                         continue;
 256                 SDEV_HOLD(dv);
 257                 /* remove the cache node */
 258                 (void) sdev_cache_update(ddv, &dv, dv->sdev_name,
 259                     SDEV_CACHE_DELETE);
 260                 SDEV_RELE(dv);
 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 };