xref: /illumos-gate/usr/src/uts/common/fs/dev/sdev_ptsops.c (revision d48be21240dfd051b689384ce2b23479d757f2d8)
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