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
51static 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
60struct vnodeops		*devpts_vnodeops;
61
62struct vnodeops *
63devpts_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 */
73static int
74devpts_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*/
101int
102devpts_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*/
163static int
164devpts_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 */
219static void
220devpts_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) == 0) {
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*/
290static int
291devpts_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*/
330static int
331devpts_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*/
367static int
368devpts_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
380static int
381devpts_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*/
391static int
392devpts_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 */
406const 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};
418