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