Home | History | Annotate | Download | only in os
      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