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