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 }; 411