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