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