xref: /illumos-gate/usr/src/uts/common/os/tlabel.c (revision fcf3ce44)
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 #include <sys/types.h>
29 #include <sys/param.h>
30 #include <sys/cmn_err.h>
31 #include <sys/systm.h>
32 #include <sys/cred.h>
33 #include <sys/modctl.h>
34 #include <sys/vfs.h>
35 #include <sys/vnode.h>
36 #include <sys/tiuser.h>
37 #include <sys/kmem.h>
38 #include <sys/pathname.h>
39 #include <sys/zone.h>
40 #include <sys/tsol/label.h>
41 #include <sys/tsol/tnet.h>
42 #include <sys/fs/lofs_node.h>
43 #include <inet/ip6.h>
44 #include <rpc/auth.h>
45 #include <rpc/clnt.h>
46 #include <nfs/nfs.h>
47 #include <nfs/nfs4.h>
48 #include <nfs/nfs_clnt.h>
49 #include <sys/dnlc.h>
50 
51 
52 int sys_labeling = 0;			/* the default is "off" */
53 
54 static kmem_cache_t *tslabel_cache;
55 ts_label_t *l_admin_low;
56 ts_label_t *l_admin_high;
57 
58 uint32_t default_doi = DEFAULT_DOI;
59 
60 /*
61  * Initialize labels infrastructure.
62  * This is called during startup() time (before vfs_mntroot) by thread_init().
63  * It has to be called early so that the is_system_labeled() function returns
64  * the right value when called by the networking code on a diskless boot.
65  */
66 void
67 label_init(void)
68 {
69 	bslabel_t label;
70 
71 	/*
72 	 * sys_labeling will default to "off" unless it is overridden
73 	 * in /etc/system.
74 	 */
75 
76 	tslabel_cache = kmem_cache_create("tslabel_cache", sizeof (ts_label_t),
77 	    0, NULL, NULL, NULL, NULL, NULL, 0);
78 	bsllow(&label);
79 	l_admin_low = labelalloc(&label, default_doi, KM_SLEEP);
80 	bslhigh(&label);
81 	l_admin_high = labelalloc(&label, default_doi, KM_SLEEP);
82 }
83 
84 /*
85  * Allocate new ts_label_t.
86  */
87 ts_label_t *
88 labelalloc(const bslabel_t *val, uint32_t doi, int flag)
89 {
90 	ts_label_t *lab = kmem_cache_alloc(tslabel_cache, flag);
91 
92 	if (lab != NULL) {
93 		lab->tsl_ref = 1;
94 		lab->tsl_doi = doi;
95 		lab->tsl_flags = 0;
96 		if (val == NULL)
97 			bzero(&lab->tsl_label, sizeof (bslabel_t));
98 		else
99 			bcopy(val, &lab->tsl_label,  sizeof (bslabel_t));
100 	}
101 	return (lab);
102 }
103 
104 /*
105  * Put a hold on a label structure.
106  */
107 void
108 label_hold(ts_label_t *lab)
109 {
110 	atomic_add_32(&lab->tsl_ref, 1);
111 }
112 
113 /*
114  * Release previous hold on a label structure.  Free it if refcnt == 0.
115  */
116 void
117 label_rele(ts_label_t *lab)
118 {
119 	if (atomic_add_32_nv(&lab->tsl_ref, -1) == 0)
120 		kmem_cache_free(tslabel_cache, lab);
121 }
122 
123 bslabel_t *
124 label2bslabel(ts_label_t *lab)
125 {
126 	return (&lab->tsl_label);
127 }
128 
129 
130 uint32_t
131 label2doi(ts_label_t *lab)
132 {
133 	return (lab->tsl_doi);
134 }
135 
136 /*
137  * Compare labels. Return 1 if equal, 0 otherwise.
138  */
139 boolean_t
140 label_equal(const ts_label_t *l1, const ts_label_t *l2)
141 {
142 	return ((l1->tsl_doi == l2->tsl_doi) &&
143 	    blequal(&l1->tsl_label, &l2->tsl_label));
144 }
145 
146 /*
147  * There's no protocol today to obtain the label from the server.
148  * So we rely on conventions: zones, zone names, and zone paths
149  * must match across TX servers and their TX clients.  Now use
150  * the exported name to find the equivalent local zone and its
151  * label.  Caller is responsible for doing a label_rele of the
152  * returned ts_label.
153  */
154 ts_label_t *
155 getflabel_cipso(vfs_t *vfsp)
156 {
157 	zone_t	*reszone;
158 	zone_t	*new_reszone;
159 	char	*nfspath, *respath;
160 	refstr_t	*resource_ref;
161 	boolean_t	treat_abs = B_FALSE;
162 
163 	if (vfsp->vfs_resource == NULL)
164 		return (NULL);			/* error */
165 	resource_ref = vfs_getresource(vfsp);
166 
167 	nfspath = (char *)refstr_value(resource_ref);
168 	respath = strchr(nfspath, ':');		/* skip server name */
169 	if (respath)
170 		respath++;			/* skip over ":" */
171 	if (*respath != '/') {
172 		/* treat path as absolute but it doesn't have leading '/' */
173 		treat_abs = B_TRUE;
174 	}
175 
176 	reszone = zone_find_by_any_path(respath, treat_abs);
177 	if (reszone == global_zone) {
178 		refstr_rele(resource_ref);
179 		label_hold(l_admin_low);
180 		zone_rele(reszone);
181 		return (l_admin_low);
182 	}
183 
184 	/*
185 	 * Skip over zonepath (not including "root"), e.g. /zone/internal
186 	 */
187 	respath += reszone->zone_rootpathlen - 7;
188 	if (treat_abs)
189 		respath--;			/* no leading '/' to skip */
190 	if (strncmp(respath, "/root/", 6) == 0) {
191 		/* Check if we now have something like "/zone/public/" */
192 
193 		respath += 5;			/* skip "/root" first */
194 		new_reszone = zone_find_by_any_path(respath, B_FALSE);
195 		if (new_reszone != global_zone) {
196 			zone_rele(reszone);
197 			reszone = new_reszone;
198 		} else {
199 			zone_rele(new_reszone);
200 		}
201 	}
202 
203 	refstr_rele(resource_ref);
204 	label_hold(reszone->zone_slabel);
205 	zone_rele(reszone);
206 
207 	return (reszone->zone_slabel);
208 }
209 
210 static ts_label_t *
211 getflabel_nfs(vfs_t *vfsp)
212 {
213 	bslabel_t	*server_sl;
214 	ts_label_t	*srv_label;
215 	tsol_tpc_t	*tp;
216 	int		addr_type;
217 	void		*ipaddr;
218 	struct servinfo *svp;
219 	struct netbuf	*addr;
220 	struct knetconfig *knconf;
221 	mntinfo_t	*mi;
222 
223 	mi = VFTOMI(vfsp);
224 	svp = mi->mi_curr_serv;
225 	addr = &svp->sv_addr;
226 	knconf = svp->sv_knconf;
227 
228 	if (strcmp(knconf->knc_protofmly, NC_INET) == 0) {
229 		addr_type = IPV4_VERSION;
230 		/* LINTED: following cast to ipaddr is OK */
231 		ipaddr = &((struct sockaddr_in *)addr->buf)->sin_addr;
232 	} else if (strcmp(knconf->knc_protofmly, NC_INET6) == 0) {
233 		addr_type = IPV6_VERSION;
234 		/* LINTED: following cast to ipaddr is OK */
235 		ipaddr = &((struct sockaddr_in6 *)addr->buf)->sin6_addr;
236 	} else {
237 		goto errout;
238 	}
239 
240 	tp = find_tpc(ipaddr, addr_type, B_FALSE);
241 	if (tp == NULL)
242 		goto errout;
243 
244 	if (tp->tpc_tp.host_type == SUN_CIPSO) {
245 		TPC_RELE(tp);
246 		return (getflabel_cipso(vfsp));
247 	}
248 
249 	if (tp->tpc_tp.host_type != UNLABELED)
250 		goto errout;
251 
252 	server_sl = &tp->tpc_tp.tp_def_label;
253 	srv_label = labelalloc(server_sl, default_doi, KM_SLEEP);
254 
255 	TPC_RELE(tp);
256 
257 	return (srv_label);
258 
259 errout:
260 	return (NULL);
261 }
262 
263 /*
264  * getflabel -
265  *
266  * Return pointer to the ts_label associated with the specified file,
267  * or returns NULL if error occurs.  Caller is responsible for doing
268  * a label_rele of the ts_label.
269  */
270 ts_label_t *
271 getflabel(vnode_t *vp)
272 {
273 	vfs_t		*vfsp, *rvfsp;
274 	vnode_t		*rvp, *rvp2;
275 	zone_t		*zone;
276 	ts_label_t	*zl;
277 	boolean_t	vfs_is_held = B_FALSE;
278 	char		vpath[MAXPATHLEN];
279 
280 	ASSERT(vp);
281 	vfsp = vp->v_vfsp;
282 	if (vfsp == NULL)
283 		return (NULL);
284 
285 	rvp = vp;
286 
287 	/*
288 	 * Traverse lofs mounts and fattach'es to get the real vnode
289 	 */
290 	if (VOP_REALVP(rvp, &rvp2, NULL) == 0)
291 		rvp = rvp2;
292 
293 	rvfsp = rvp->v_vfsp;
294 
295 	/* rvp/rvfsp now represent the real vnode/vfs we will be using */
296 
297 	/* Go elsewhere to handle all nfs files. */
298 	if (strncmp(vfssw[rvfsp->vfs_fstype].vsw_name, "nfs", 3) == 0)
299 		return (getflabel_nfs(rvfsp));
300 
301 	/*
302 	 * Fast path, for objects in a labeled zone: everything except
303 	 * for lofs/nfs will be just the label of that zone.
304 	 */
305 	if ((rvfsp->vfs_zone != NULL) && (rvfsp->vfs_zone != global_zone)) {
306 		if ((strcmp(vfssw[rvfsp->vfs_fstype].vsw_name,
307 		    "lofs") != 0)) {
308 			zone = rvfsp->vfs_zone;
309 			zone_hold(zone);
310 			goto zone_out;		/* return this label */
311 		}
312 	}
313 
314 	if (vnodetopath(rootdir, rvp, vpath, sizeof (vpath), kcred) != 0) {
315 		return (NULL);
316 	}
317 
318 	/*
319 	 * Sanity check - vpath may be weird for some cases, like devices.
320 	 */
321 	if (*vpath != '/') {
322 		zone = curproc->p_zone;
323 		zone_hold(zone);
324 		goto zone_out;
325 	}
326 
327 	/*
328 	 * If a mountpoint exists, hold the vfs while we reference it.
329 	 * Otherwise if mountpoint is NULL it should not be held (e.g.,
330 	 * a hold/release on spec_vfs would result in an attempted free
331 	 * and panic.)
332 	 */
333 	if (vfsp->vfs_mntpt != NULL) {
334 		VFS_HOLD(vfsp);
335 		vfs_is_held = B_TRUE;
336 	}
337 
338 	zone = zone_find_by_any_path(vpath, B_FALSE);
339 
340 	/*
341 	 * If the vnode source zone is properly set to a non-global zone, or
342 	 * any zone if the mount is R/W, then use the label of that zone.
343 	 */
344 	if ((zone != global_zone) || ((vfsp->vfs_flag & VFS_RDONLY) != 0))
345 		goto zone_out;		/* return this label */
346 
347 	/*
348 	 * Otherwise, if we're not in the global zone, use the label of
349 	 * our zone.
350 	 */
351 	if ((zone = curproc->p_zone) != global_zone) {
352 		zone_hold(zone);
353 		goto zone_out;		/* return this label */
354 	}
355 
356 	/*
357 	 * We're in the global zone and the mount is R/W ... so the file
358 	 * may actually be in the global zone -- or in the root of any zone.
359 	 * Always build our own path for the file, to be sure it's simplified
360 	 * (i.e., no ".", "..", "//", and so on).
361 	 */
362 
363 	zone_rele(zone);
364 	zone = zone_find_by_any_path(vpath, B_FALSE);
365 
366 zone_out:
367 	if ((curproc->p_zone == global_zone) && (zone == global_zone)) {
368 		vfs_t		*nvfs;
369 		boolean_t	exported = B_FALSE;
370 		refstr_t	*mntpt_ref;
371 		char		*mntpt;
372 
373 		/*
374 		 * File is in the global zone - check whether it's admin_high.
375 		 * If it's in a filesys that was exported from the global zone,
376 		 * it's admin_low by definition.  Otherwise, if it's in a
377 		 * filesys that's NOT exported to any zone, it's admin_high.
378 		 *
379 		 * And for these files if there wasn't a valid mount resource,
380 		 * the file must be admin_high (not exported, probably a global
381 		 * zone device).
382 		 */
383 		if (!vfs_is_held)
384 			goto out_high;
385 
386 		mntpt_ref = vfs_getmntpoint(vfsp);
387 		mntpt = (char *)refstr_value(mntpt_ref);
388 
389 		if ((mntpt != NULL) && (*mntpt == '/')) {
390 			zone_t	*to_zone;
391 
392 			to_zone = zone_find_by_any_path(mntpt, B_FALSE);
393 			zone_rele(to_zone);
394 			if (to_zone != global_zone) {
395 				/* force admin_low */
396 				exported = B_TRUE;
397 			}
398 		}
399 		if (mntpt_ref)
400 			refstr_rele(mntpt_ref);
401 
402 		if (!exported) {
403 			size_t	plen = strlen(vpath);
404 
405 			vfs_list_read_lock();
406 			nvfs = vfsp->vfs_next;
407 			while (nvfs != vfsp) {
408 				const char	*rstr;
409 				size_t		rlen = 0;
410 
411 				rstr = refstr_value(nvfs->vfs_resource);
412 				if (rstr != NULL)
413 					rlen = strlen(rstr);
414 
415 				/*
416 				 * Check for a match: does this vfs correspond
417 				 * to our global zone file path?  I.e., check
418 				 * if the resource string of this vfs is a
419 				 * prefix of our path.
420 				 */
421 				if ((rlen > 0) && (rlen <= plen) &&
422 				    (strncmp(rstr, vpath, rlen) == 0) &&
423 				    (vpath[rlen] == '/' ||
424 				    vpath[rlen] == '\0')) {
425 					/* force admin_low */
426 					exported = B_TRUE;
427 					break;
428 				}
429 				nvfs = nvfs->vfs_next;
430 			}
431 			vfs_list_unlock();
432 		}
433 
434 		if (!exported)
435 			goto out_high;
436 	}
437 
438 	if (vfs_is_held)
439 		VFS_RELE(vfsp);
440 
441 	/*
442 	 * Now that we have the "home" zone for the file, return the slabel
443 	 * of that zone.
444 	 */
445 	zl = zone->zone_slabel;
446 	label_hold(zl);
447 	zone_rele(zone);
448 	return (zl);
449 
450 out_high:
451 	if (vfs_is_held)
452 		VFS_RELE(vfsp);
453 
454 	label_hold(l_admin_high);
455 	zone_rele(zone);
456 	return (l_admin_high);
457 }
458 
459 static int
460 cgetlabel(bslabel_t *label_p, vnode_t *vp)
461 {
462 	ts_label_t	*tsl;
463 	int		error = 0;
464 
465 	if ((tsl = getflabel(vp)) == NULL)
466 		return (EIO);
467 
468 	if (copyout((caddr_t)label2bslabel(tsl), (caddr_t)label_p,
469 	    sizeof (*(label_p))) != 0)
470 		error = EFAULT;
471 
472 	label_rele(tsl);
473 	return (error);
474 }
475 
476 /*
477  * fgetlabel(2TSOL) - get file label
478  * getlabel(2TSOL) - get file label
479  */
480 int
481 getlabel(const char *path, bslabel_t *label_p)
482 {
483 	struct		vnode	*vp;
484 	char		*spath;
485 	int		error;
486 
487 	/* Sanity check arguments */
488 	if (path == NULL)
489 		return (set_errno(EINVAL));
490 
491 	spath = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
492 	if ((error = copyinstr(path, spath, MAXPATHLEN, NULL)) != 0) {
493 		kmem_free(spath, MAXPATHLEN);
494 		return (set_errno(error));
495 	}
496 
497 	if (error = lookupname(spath, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp)) {
498 		kmem_free(spath, MAXPATHLEN);
499 		return (set_errno(error));
500 	}
501 	kmem_free(spath, MAXPATHLEN);
502 
503 	error = cgetlabel(label_p, vp);
504 
505 	VN_RELE(vp);
506 	if (error != 0)
507 		return (set_errno(error));
508 	else
509 		return (0);
510 }
511 
512 int
513 fgetlabel(int fd, bslabel_t *label_p)
514 {
515 	file_t		*fp;
516 	int		error;
517 
518 	if ((fp = getf(fd)) == NULL)
519 		return (set_errno(EBADF));
520 
521 	error = cgetlabel(label_p, fp->f_vnode);
522 	releasef(fd);
523 
524 	if (error != 0)
525 		return (set_errno(error));
526 	else
527 		return (0);
528 }
529 
530 /*
531  * Used by NFSv3 and NFSv4 server to query label of
532  * a pathname component during lookup/access ops.
533  */
534 ts_label_t *
535 nfs_getflabel(vnode_t *vp)
536 {
537 	zone_t *zone;
538 	ts_label_t *zone_label;
539 	char path[MAXNAMELEN];
540 	vnode_t *pvp, *tvp;
541 
542 	mutex_enter(&vp->v_lock);
543 	/*
544 	 * mount traverse has been done by caller
545 	 * before calling this routine.
546 	 */
547 	ASSERT(!vn_ismntpt(vp));
548 	if (vp->v_path != NULL) {
549 		zone = zone_find_by_any_path(vp->v_path, B_FALSE);
550 		mutex_exit(&vp->v_lock);
551 	} else {
552 		/*
553 		 * v_path not cached. Since we rely on path
554 		 * of an obj to get its label, we need to get
555 		 * path corresponding to the parent vnode.
556 		 */
557 		tvp = vp;
558 		do {
559 			mutex_exit(&tvp->v_lock);
560 			if ((pvp = dnlc_reverse_lookup(tvp, path,
561 			    sizeof (path))) == NULL)
562 				return (NULL);
563 			mutex_enter(&pvp->v_lock);
564 			tvp = pvp;
565 		} while (pvp->v_path == NULL);
566 		zone = zone_find_by_any_path(pvp->v_path, B_FALSE);
567 		mutex_exit(&pvp->v_lock);
568 	}
569 	/*
570 	 * Caller has verified that the file is either
571 	 * exported or visible. So if the path falls in
572 	 * global zone, admin_low is returned; otherwise
573 	 * the zone's label is returned.
574 	 */
575 	zone_label = zone->zone_slabel;
576 	label_hold(zone_label);
577 	zone_rele(zone);
578 	return (zone_label);
579 }
580