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