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