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; 278 vnode_t *rvp, *rvp2; 279 zone_t *zone; 280 ts_label_t *zl; 281 boolean_t vfs_is_held = B_FALSE; 282 char vpath[MAXPATHLEN]; 283 284 ASSERT(vp); 285 vfsp = vp->v_vfsp; 286 if (vfsp == NULL) 287 return (NULL); 288 289 rvp = vp; 290 291 /* 292 * Traverse lofs mounts and fattach'es to get the real vnode 293 */ 294 if (VOP_REALVP(rvp, &rvp2) == 0) 295 rvp = rvp2; 296 297 rvfsp = rvp->v_vfsp; 298 299 /* rvp/rvfsp now represent the real vnode/vfs we will be using */ 300 301 /* Go elsewhere to handle all nfs files. */ 302 if (strncmp(vfssw[rvfsp->vfs_fstype].vsw_name, "nfs", 3) == 0) 303 return (getflabel_nfs(rvfsp)); 304 305 /* 306 * Fast path, for objects in a labeled zone: everything except 307 * for lofs/nfs will be just the label of that zone. 308 */ 309 if ((rvfsp->vfs_zone != NULL) && (rvfsp->vfs_zone != global_zone)) { 310 if ((strcmp(vfssw[rvfsp->vfs_fstype].vsw_name, 311 "lofs") != 0)) { 312 zone = rvfsp->vfs_zone; 313 zone_hold(zone); 314 goto zone_out; /* return this label */ 315 } 316 } 317 318 if (vnodetopath(rootdir, rvp, vpath, sizeof (vpath), kcred) != 0) { 319 return (NULL); 320 } 321 322 /* 323 * Sanity check - vpath may be weird for some cases, like devices. 324 */ 325 if (*vpath != '/') { 326 zone = curproc->p_zone; 327 zone_hold(zone); 328 goto zone_out; 329 } 330 331 /* 332 * If a mountpoint exists, hold the vfs while we reference it. 333 * Otherwise if mountpoint is NULL it should not be held (e.g., 334 * a hold/release on spec_vfs would result in an attempted free 335 * and panic.) 336 */ 337 if (vfsp->vfs_mntpt != NULL) { 338 VFS_HOLD(vfsp); 339 vfs_is_held = B_TRUE; 340 } 341 342 zone = zone_find_by_any_path(vpath, B_FALSE); 343 344 /* 345 * If the vnode source zone is properly set to a non-global zone, or 346 * any zone if the mount is R/W, then use the label of that zone. 347 */ 348 if ((zone != global_zone) || ((vfsp->vfs_flag & VFS_RDONLY) != 0)) 349 goto zone_out; /* return this label */ 350 351 /* 352 * Otherwise, if we're not in the global zone, use the label of 353 * our zone. 354 */ 355 if ((zone = curproc->p_zone) != global_zone) { 356 zone_hold(zone); 357 goto zone_out; /* return this label */ 358 } 359 360 /* 361 * We're in the global zone and the mount is R/W ... so the file 362 * may actually be in the global zone -- or in the root of any zone. 363 * Always build our own path for the file, to be sure it's simplified 364 * (i.e., no ".", "..", "//", and so on). 365 */ 366 367 zone_rele(zone); 368 zone = zone_find_by_any_path(vpath, B_FALSE); 369 370 zone_out: 371 if ((curproc->p_zone == global_zone) && (zone == global_zone)) { 372 vfs_t *nvfs; 373 boolean_t exported = B_FALSE; 374 refstr_t *mntpt_ref; 375 char *mntpt; 376 377 /* 378 * File is in the global zone - check whether it's admin_high. 379 * If it's in a filesys that was exported from the global zone, 380 * it's admin_low by definition. Otherwise, if it's in a 381 * filesys that's NOT exported to any zone, it's admin_high. 382 * 383 * And for these files if there wasn't a valid mount resource, 384 * the file must be admin_high (not exported, probably a global 385 * zone device). 386 */ 387 if (!vfs_is_held) 388 goto out_high; 389 390 mntpt_ref = vfs_getmntpoint(vfsp); 391 mntpt = (char *)refstr_value(mntpt_ref); 392 393 if ((mntpt != NULL) && (*mntpt == '/')) { 394 zone_t *to_zone; 395 396 to_zone = zone_find_by_any_path(mntpt, B_FALSE); 397 zone_rele(to_zone); 398 if (to_zone != global_zone) { 399 /* force admin_low */ 400 exported = B_TRUE; 401 } 402 } 403 if (mntpt_ref) 404 refstr_rele(mntpt_ref); 405 406 if (!exported) { 407 size_t plen = strlen(vpath); 408 409 vfs_list_read_lock(); 410 nvfs = vfsp->vfs_next; 411 while (nvfs != vfsp) { 412 const char *rstr; 413 size_t rlen = 0; 414 415 rstr = refstr_value(nvfs->vfs_resource); 416 if (rstr != NULL) 417 rlen = strlen(rstr); 418 419 /* 420 * Check for a match: does this vfs correspond 421 * to our global zone file path? I.e., check 422 * if the resource string of this vfs is a 423 * prefix of our path. 424 */ 425 if ((rlen > 0) && (rlen <= plen) && 426 (strncmp(rstr, vpath, rlen) == 0) && 427 (vpath[rlen] == '/' || 428 vpath[rlen] == '\0')) { 429 /* force admin_low */ 430 exported = B_TRUE; 431 break; 432 } 433 nvfs = nvfs->vfs_next; 434 } 435 vfs_list_unlock(); 436 } 437 438 if (!exported) 439 goto out_high; 440 } 441 442 if (vfs_is_held) 443 VFS_RELE(vfsp); 444 445 /* 446 * Now that we have the "home" zone for the file, return the slabel 447 * of that zone. 448 */ 449 zl = zone->zone_slabel; 450 label_hold(zl); 451 zone_rele(zone); 452 return (zl); 453 454 out_high: 455 if (vfs_is_held) 456 VFS_RELE(vfsp); 457 458 label_hold(l_admin_high); 459 zone_rele(zone); 460 return (l_admin_high); 461 } 462 463 static int 464 cgetlabel(bslabel_t *label_p, vnode_t *vp) 465 { 466 ts_label_t *tsl; 467 int error = 0; 468 469 if ((tsl = getflabel(vp)) == NULL) 470 return (EIO); 471 472 if (copyout((caddr_t)label2bslabel(tsl), (caddr_t)label_p, 473 sizeof (*(label_p))) != 0) 474 error = EFAULT; 475 476 label_rele(tsl); 477 return (error); 478 } 479 480 /* 481 * fgetlabel(2TSOL) - get file label 482 * getlabel(2TSOL) - get file label 483 */ 484 int 485 getlabel(const char *path, bslabel_t *label_p) 486 { 487 struct vnode *vp; 488 char *spath; 489 int error; 490 491 /* Sanity check arguments */ 492 if (path == NULL) 493 return (set_errno(EINVAL)); 494 495 spath = kmem_zalloc(MAXPATHLEN, KM_SLEEP); 496 if ((error = copyinstr(path, spath, MAXPATHLEN, NULL)) != 0) { 497 kmem_free(spath, MAXPATHLEN); 498 return (set_errno(error)); 499 } 500 501 if (error = lookupname(spath, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp)) { 502 kmem_free(spath, MAXPATHLEN); 503 return (set_errno(error)); 504 } 505 kmem_free(spath, MAXPATHLEN); 506 507 error = cgetlabel(label_p, vp); 508 509 VN_RELE(vp); 510 if (error != 0) 511 return (set_errno(error)); 512 else 513 return (0); 514 } 515 516 int 517 fgetlabel(int fd, bslabel_t *label_p) 518 { 519 file_t *fp; 520 int error; 521 522 if ((fp = getf(fd)) == NULL) 523 return (set_errno(EBADF)); 524 525 error = cgetlabel(label_p, fp->f_vnode); 526 releasef(fd); 527 528 if (error != 0) 529 return (set_errno(error)); 530 else 531 return (0); 532 } 533 534 /* 535 * Used by NFSv4 to query label of a pathname 536 * component during lookup/access ops. 537 */ 538 ts_label_t * 539 nfs4_getflabel(vnode_t *vp) 540 { 541 zone_t *zone; 542 ts_label_t *zone_label; 543 char path[MAXNAMELEN]; 544 vnode_t *pvp, *tvp; 545 546 mutex_enter(&vp->v_lock); 547 /* 548 * mount traverse has been done by caller 549 * before calling this routine. 550 */ 551 ASSERT(!vn_ismntpt(vp)); 552 if (vp->v_path != NULL) { 553 zone = zone_find_by_any_path(vp->v_path, B_FALSE); 554 mutex_exit(&vp->v_lock); 555 } else { 556 /* 557 * v_path not cached. Since we rely on path 558 * of an obj to get its label, we need to get 559 * path corresponding to the parent vnode. 560 */ 561 tvp = vp; 562 do { 563 mutex_exit(&tvp->v_lock); 564 if ((pvp = dnlc_reverse_lookup(tvp, path, 565 sizeof (path))) == NULL) 566 return (NULL); 567 mutex_enter(&pvp->v_lock); 568 tvp = pvp; 569 } while (pvp->v_path == NULL); 570 zone = zone_find_by_any_path(pvp->v_path, B_FALSE); 571 mutex_exit(&pvp->v_lock); 572 } 573 /* 574 * Caller has verified that the file is either 575 * exported or visible. So if the path falls in 576 * global zone, admin_low is returned; otherwise 577 * the zone's label is returned. 578 */ 579 zone_label = zone->zone_slabel; 580 label_hold(zone_label); 581 zone_rele(zone); 582 return (zone_label); 583 } 584