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