xref: /illumos-gate/usr/src/uts/common/fs/dev/sdev_ptsops.c (revision cbcfaf83d8f1bf6aa00c793903a55685cac2c548)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * vnode ops for the /dev/pts directory
30  *	The lookup is based on the internal pty table. We also
31  *	override readdir in order to delete pts nodes no longer
32  *	in use.
33  */
34 
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/sysmacros.h>
38 #include <sys/sunndi.h>
39 #include <fs/fs_subr.h>
40 #include <sys/fs/dv_node.h>
41 #include <sys/fs/sdev_impl.h>
42 #include <sys/policy.h>
43 #include <sys/ptms.h>
44 #include <sys/stat.h>
45 
46 #define	DEVPTS_UID_DEFAULT	0
47 #define	DEVPTS_GID_DEFAULT	3
48 #define	DEVPTS_DEVMODE_DEFAULT	(0620)
49 
50 #define	isdigit(ch)	((ch) >= '0' && (ch) <= '9')
51 
52 static vattr_t devpts_vattr = {
53 	AT_TYPE|AT_MODE|AT_UID|AT_GID,		/* va_mask */
54 	VCHR,					/* va_type */
55 	S_IFCHR | DEVPTS_DEVMODE_DEFAULT,	/* va_mode */
56 	DEVPTS_UID_DEFAULT,			/* va_uid */
57 	DEVPTS_GID_DEFAULT,			/* va_gid */
58 	0					/* 0 hereafter */
59 };
60 
61 struct vnodeops		*devpts_vnodeops;
62 
63 struct vnodeops *
64 devpts_getvnodeops(void)
65 {
66 	return (devpts_vnodeops);
67 }
68 
69 /*
70  * Convert string to minor number. Some care must be taken
71  * as we are processing user input. Catch cases like
72  * /dev/pts/4foo and /dev/pts/-1
73  */
74 static int
75 devpts_strtol(const char *nm, minor_t *mp)
76 {
77 	long uminor = 0;
78 	char *endptr = NULL;
79 
80 	if (nm == NULL || !isdigit(*nm))
81 		return (EINVAL);
82 
83 	*mp = 0;
84 	if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
85 	    *endptr != '\0' || uminor < 0) {
86 		return (EINVAL);
87 	}
88 
89 	*mp = (minor_t)uminor;
90 	return (0);
91 }
92 
93 /*
94  * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
95  * This serves two purposes
96  *	- only valid pts nodes are returned during lookup() and readdir().
97  *	- since pts sdev_nodes are not actively destroyed when a pty goes
98  *	  away, we use the validator to do deferred cleanup i.e. when such
99  *	  nodes are encountered during subsequent lookup() and readdir().
100  */
101 /*ARGSUSED*/
102 int
103 devpts_validate(struct sdev_node *dv)
104 {
105 	minor_t min;
106 	uid_t uid;
107 	gid_t gid;
108 	timestruc_t now;
109 	char *nm = dv->sdev_name;
110 
111 	ASSERT(!(dv->sdev_flags & SDEV_STALE));
112 	ASSERT(dv->sdev_state == SDEV_READY);
113 
114 	/* validate only READY nodes */
115 	if (dv->sdev_state != SDEV_READY) {
116 		sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
117 		    nm, (void *)dv));
118 		return (SDEV_VTOR_SKIP);
119 	}
120 
121 	if (devpts_strtol(nm, &min) != 0) {
122 		sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
123 		return (SDEV_VTOR_INVALID);
124 	}
125 
126 	/*
127 	 * Check if pts driver is attached
128 	 */
129 	if (ptms_slave_attached() == (major_t)-1) {
130 		sdcmn_err7(("devpts_validate: slave not attached\n"));
131 		return (SDEV_VTOR_INVALID);
132 	}
133 
134 	if (ptms_minor_valid(min, &uid, &gid) == 0) {
135 		if (ptms_minor_exists(min)) {
136 			sdcmn_err7(("devpts_validate: valid in different zone "
137 			    "%s\n", nm));
138 			return (SDEV_VTOR_SKIP);
139 		} else {
140 			sdcmn_err7(("devpts_validate: %s not valid pty\n",
141 			    nm));
142 			return (SDEV_VTOR_INVALID);
143 		}
144 	}
145 
146 	ASSERT(dv->sdev_attr);
147 	if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
148 		ASSERT(uid >= 0);
149 		ASSERT(gid >= 0);
150 		dv->sdev_attr->va_uid = uid;
151 		dv->sdev_attr->va_gid = gid;
152 		gethrestime(&now);
153 		dv->sdev_attr->va_atime = now;
154 		dv->sdev_attr->va_mtime = now;
155 		dv->sdev_attr->va_ctime = now;
156 		sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
157 	}
158 
159 	return (SDEV_VTOR_VALID);
160 }
161 
162 /*
163  * This callback is invoked from devname_lookup_func() to create
164  * a pts entry when the node is not found in the cache.
165  */
166 /*ARGSUSED*/
167 static int
168 devpts_create_rvp(struct sdev_node *ddv, char *nm,
169     void **arg, cred_t *cred, void *whatever, char *whichever)
170 {
171 	minor_t min;
172 	major_t maj;
173 	uid_t uid;
174 	gid_t gid;
175 	timestruc_t now;
176 	struct vattr *vap = (struct vattr *)arg;
177 
178 	if (devpts_strtol(nm, &min) != 0) {
179 		sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
180 		return (-1);
181 	}
182 
183 	/*
184 	 * Check if pts driver is attached and if it is
185 	 * get the major number.
186 	 */
187 	maj = ptms_slave_attached();
188 	if (maj == (major_t)-1) {
189 		sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
190 		return (-1);
191 	}
192 
193 	/*
194 	 * Only allow creation of ptys allocated to our zone
195 	 */
196 	if (!ptms_minor_valid(min, &uid, &gid)) {
197 		sdcmn_err7(("devpts_create_rvp: %s not valid pty"
198 		    "or not valid in this zone\n", nm));
199 		return (-1);
200 	}
201 
202 
203 	/*
204 	 * This is a valid pty (at least at this point in time).
205 	 * Create the node by setting the attribute. The rest
206 	 * is taken care of by devname_lookup_func().
207 	 */
208 	*vap = devpts_vattr;
209 	vap->va_rdev = makedevice(maj, min);
210 	ASSERT(uid >= 0);
211 	ASSERT(gid >= 0);
212 	vap->va_uid = uid;
213 	vap->va_gid = gid;
214 	gethrestime(&now);
215 	vap->va_atime = now;
216 	vap->va_mtime = now;
217 	vap->va_ctime = now;
218 
219 	return (0);
220 }
221 
222 /*
223  * Clean pts sdev_nodes that are no longer valid.
224  */
225 static void
226 devpts_prunedir(struct sdev_node *ddv)
227 {
228 	struct vnode *vp;
229 	struct sdev_node *dv, *next = NULL;
230 	int (*vtor)(struct sdev_node *) = NULL;
231 
232 	ASSERT(ddv->sdev_flags & SDEV_VTOR);
233 
234 	vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
235 	ASSERT(vtor);
236 
237 	if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
238 		rw_exit(&ddv->sdev_contents);
239 		rw_enter(&ddv->sdev_contents, RW_WRITER);
240 	}
241 
242 	for (dv = ddv->sdev_dot; dv; dv = next) {
243 		next = dv->sdev_next;
244 
245 		/* skip stale nodes */
246 		if (dv->sdev_flags & SDEV_STALE)
247 			continue;
248 
249 		/* validate and prune only ready nodes */
250 		if (dv->sdev_state != SDEV_READY)
251 			continue;
252 
253 		switch (vtor(dv)) {
254 		case SDEV_VTOR_VALID:
255 		case SDEV_VTOR_SKIP:
256 			continue;
257 		case SDEV_VTOR_INVALID:
258 			sdcmn_err7(("prunedir: destroy invalid "
259 			    "node: %s(%p)\n", dv->sdev_name, (void *)dv));
260 			break;
261 		}
262 		vp = SDEVTOV(dv);
263 		if (vp->v_count > 0)
264 			continue;
265 		SDEV_HOLD(dv);
266 		/* remove the cache node */
267 		(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
268 		    SDEV_CACHE_DELETE);
269 	}
270 	rw_downgrade(&ddv->sdev_contents);
271 }
272 
273 /*
274  * Lookup for /dev/pts directory
275  *	If the entry does not exist, the devpts_create_rvp() callback
276  *	is invoked to create it. Nodes do not persist across reboot.
277  *
278  * There is a potential denial of service here via
279  * fattach on top of a /dev/pts node - any permission changes
280  * applied to the node, apply to the fattached file and not
281  * to the underlying pts node. As a result when the previous
282  * user fdetaches, the pts node is still owned by the previous
283  * owner. To prevent this we don't allow fattach() on top of a pts
284  * node. This is done by a modification in the namefs filesystem
285  * where we check if the underlying node has the /dev/pts vnodeops.
286  * We do this via VOP_REALVP() on the underlying specfs node.
287  * sdev_nodes currently don't have a realvp. If a realvp is ever
288  * created for sdev_nodes, then VOP_REALVP() will return the
289  * actual realvp (possibly a ufs vnode). This will defeat the check
290  * in namefs code which checks if VOP_REALVP() returns a devpts
291  * node. We add an ASSERT here in /dev/pts lookup() to check for
292  * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
293  * change the code in the namefs filesystem code (in nm_mount()) to
294  * access the realvp of the specfs node directly instead of using
295  * VOP_REALVP().
296  */
297 /*ARGSUSED3*/
298 static int
299 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
300     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred)
301 {
302 	struct sdev_node *sdvp = VTOSDEV(dvp);
303 	struct sdev_node *dv;
304 	struct vnode *rvp = NULL;
305 	int error;
306 
307 	error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
308 	    SDEV_VATTR);
309 
310 	if (error == 0) {
311 		switch ((*vpp)->v_type) {
312 		case VCHR:
313 			dv = VTOSDEV(VTOS(*vpp)->s_realvp);
314 			ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp) == ENOSYS);
315 			break;
316 		case VDIR:
317 			dv = VTOSDEV(*vpp);
318 			break;
319 		default:
320 			cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
321 			    "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
322 			break;
323 		}
324 		ASSERT(SDEV_HELD(dv));
325 	}
326 
327 	return (error);
328 }
329 
330 /*
331  * We allow create to find existing nodes
332  *	- if the node doesn't exist - EROFS
333  *	- creating an existing dir read-only succeeds, otherwise EISDIR
334  *	- exclusive creates fail - EEXIST
335  */
336 /*ARGSUSED2*/
337 static int
338 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
339     int mode, struct vnode **vpp, struct cred *cred, int flag)
340 {
341 	int error;
342 	struct vnode *vp;
343 
344 	*vpp = NULL;
345 
346 	error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred);
347 	if (error == 0) {
348 		if (excl == EXCL)
349 			error = EEXIST;
350 		else if (vp->v_type == VDIR && (mode & VWRITE))
351 			error = EISDIR;
352 		else
353 			error = VOP_ACCESS(vp, mode, 0, cred);
354 
355 		if (error) {
356 			VN_RELE(vp);
357 		} else
358 			*vpp = vp;
359 	} else if (error == ENOENT) {
360 		error = EROFS;
361 	}
362 
363 	return (error);
364 }
365 
366 /*
367  * Display all instantiated pts (slave) device nodes.
368  * A /dev/pts entry will be created only after the first lookup of the slave
369  * device succeeds.
370  */
371 static int
372 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
373     int *eofp)
374 {
375 	struct sdev_node *sdvp = VTOSDEV(dvp);
376 	if (uiop->uio_offset == 0) {
377 		devpts_prunedir(sdvp);
378 	}
379 
380 	return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
381 }
382 
383 
384 static int
385 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
386 {
387 	ASSERT((protocol & AT_UID) || (protocol & AT_GID));
388 	ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
389 	    vap->va_uid, vap->va_gid);
390 	return (0);
391 
392 }
393 
394 /*ARGSUSED4*/
395 static int
396 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
397     struct cred *cred, caller_context_t *ctp)
398 {
399 	ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
400 	return (devname_setattr_func(vp, vap, flags, cred,
401 		    devpts_set_id, AT_UID|AT_GID));
402 }
403 
404 
405 /*
406  * We override lookup and readdir to build entries based on the
407  * in kernel pty table. Also override setattr/setsecattr to
408  * avoid persisting permissions.
409  */
410 const fs_operation_def_t devpts_vnodeops_tbl[] = {
411 	VOPNAME_READDIR, devpts_readdir,
412 	VOPNAME_LOOKUP, devpts_lookup,
413 	VOPNAME_CREATE, devpts_create,
414 	VOPNAME_SETATTR, devpts_setattr,
415 	VOPNAME_REMOVE, fs_nosys,
416 	VOPNAME_MKDIR, fs_nosys,
417 	VOPNAME_RMDIR, fs_nosys,
418 	VOPNAME_SYMLINK, fs_nosys,
419 	VOPNAME_SETSECATTR, fs_nosys,
420 	NULL, NULL
421 };
422