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