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