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 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Copyright 2020 Tintri by DDN, Inc. All rights reserved. 29 * Copyright 2025 RackTop Systems, Inc. 30 */ 31 32 /* 33 * Sid manipulation (stubs). 34 */ 35 36 #include <sys/atomic.h> 37 #include <sys/avl.h> 38 #include <sys/cmn_err.h> 39 #include <sys/kmem.h> 40 #include <sys/mutex.h> 41 #include <sys/sid.h> 42 #include <sys/sysmacros.h> 43 #include <sys/systm.h> 44 45 #ifdef _KERNEL 46 #include <sys/kidmap.h> 47 #endif 48 49 #include <sys/idmap.h> 50 #include <util/qsort.h> 51 52 static kmutex_t sid_lock; 53 static avl_tree_t sid_tree; 54 static boolean_t sid_inited = B_FALSE; 55 56 static ksiddomain_t 57 *ksid_enterdomain(const char *dom) 58 { 59 size_t len = strlen(dom) + 1; 60 ksiddomain_t *res; 61 62 ASSERT(MUTEX_HELD(&sid_lock)); 63 res = kmem_alloc(sizeof (ksiddomain_t), KM_SLEEP); 64 res->kd_len = (uint_t)len; 65 res->kd_name = kmem_alloc(len, KM_SLEEP); 66 bcopy(dom, res->kd_name, len); 67 68 res->kd_ref = 1; 69 70 avl_add(&sid_tree, res); 71 72 return (res); 73 } 74 75 void 76 ksid_hold(ksid_t *ks) 77 { 78 if (ks->ks_domain != NULL) 79 ksiddomain_hold(ks->ks_domain); 80 } 81 82 void 83 ksid_rele(ksid_t *ks) 84 { 85 if (ks->ks_domain != NULL) 86 ksiddomain_rele(ks->ks_domain); 87 } 88 89 void 90 ksiddomain_hold(ksiddomain_t *kd) 91 { 92 atomic_inc_32(&kd->kd_ref); 93 } 94 95 void 96 ksiddomain_rele(ksiddomain_t *kd) 97 { 98 if (atomic_dec_32_nv(&kd->kd_ref) == 0) { 99 /* 100 * The kd reference can only be incremented from 0 when 101 * the sid_lock is held; so we lock and then check need to 102 * check for 0 again. 103 */ 104 mutex_enter(&sid_lock); 105 if (kd->kd_ref == 0) { 106 avl_remove(&sid_tree, kd); 107 kmem_free(kd->kd_name, kd->kd_len); 108 kmem_free(kd, sizeof (*kd)); 109 } 110 mutex_exit(&sid_lock); 111 } 112 } 113 114 void 115 ksidlist_hold(ksidlist_t *ksl) 116 { 117 atomic_inc_32(&ksl->ksl_ref); 118 } 119 120 void 121 ksidlist_rele(ksidlist_t *ksl) 122 { 123 if (atomic_dec_32_nv(&ksl->ksl_ref) == 0) { 124 int i; 125 126 if (ksl->ksl_sorted != NULL) 127 kmem_free(ksl->ksl_sorted, 128 ksl->ksl_nsid * sizeof (ksid_t *)); 129 for (i = 0; i < ksl->ksl_nsid; i++) 130 ksid_rele(&ksl->ksl_sids[i]); 131 132 kmem_free(ksl, KSIDLIST_MEM(ksl->ksl_nsid)); 133 } 134 } 135 136 /* 137 * Linear search is more efficient for 'small' arrays. 138 * What's considered 'small' varies by system. 139 * supgroupmember() uses 16; we make ours a variable for testing. 140 */ 141 int ksl_bin_search_cutoff = 16; 142 143 boolean_t 144 ksidlist_has_sid(ksidlist_t *ksl, const char *domain, uint32_t rid) 145 { 146 int64_t hi, lo, m; 147 int cmp; 148 ksid_t *sids = ksl->ksl_sids; /* sorted by SID */ 149 150 lo = 0; 151 hi = ksl->ksl_nsid - 1; 152 153 if (hi < ksl_bin_search_cutoff) { 154 for (; lo <= hi; lo++) { 155 if (rid == sids[lo].ks_rid && 156 strcmp(domain, ksid_getdomain(&sids[lo])) == 0) 157 return (B_TRUE); 158 } 159 return (B_FALSE); 160 } 161 162 do { 163 /* This is an overflow-safe version of m = (lo + hi) / 2 */ 164 m = (int64_t)((uint64_t)(lo + hi) >> 1); 165 166 cmp = AVL_CMP(rid, sids[m].ks_rid); 167 if (cmp == 0) 168 cmp = strcmp(domain, ksid_getdomain(&sids[m])); 169 170 if (cmp > 0) 171 lo = m + 1; 172 else if (cmp < 0) 173 hi = m - 1; 174 else 175 return (B_TRUE); 176 177 } while (lo <= hi); 178 179 return (B_FALSE); 180 } 181 182 boolean_t 183 ksidlist_has_pid(ksidlist_t *ksl, uint32_t pid) 184 { 185 int64_t hi, lo, m; 186 int cmp; 187 ksid_t **sidsp = ksl->ksl_sorted; /* sorted by posix ID */ 188 189 lo = 0; 190 hi = ksl->ksl_nsid - 1; 191 192 if (hi < ksl_bin_search_cutoff) { 193 for (; lo <= hi; lo++) { 194 if (pid == ksl->ksl_sids[lo].ks_id) 195 return (B_TRUE); 196 } 197 return (B_FALSE); 198 } 199 200 do { 201 /* This is an overflow-safe version of m = (lo + hi) / 2 */ 202 m = (int64_t)((uint64_t)(lo + hi) >> 1); 203 204 cmp = AVL_CMP(pid, sidsp[m]->ks_id); 205 206 if (cmp > 0) 207 lo = m + 1; 208 else if (cmp < 0) 209 hi = m - 1; 210 else 211 return (B_TRUE); 212 213 } while (lo <= hi); 214 215 return (B_FALSE); 216 } 217 218 static int 219 ksid_cmp(const void *a, const void *b) 220 { 221 const ksiddomain_t *ap = a; 222 const ksiddomain_t *bp = b; 223 int res; 224 225 res = strcmp(ap->kd_name, bp->kd_name); 226 227 return (AVL_ISIGN(res)); 228 } 229 230 /* 231 * Lookup the named domain in the AVL tree. 232 * If no entry is found, add the domain to the AVL tree. 233 * The domain is returned held and needs to be released 234 * when done. 235 */ 236 ksiddomain_t 237 *ksid_lookupdomain(const char *dom) 238 { 239 ksiddomain_t *res; 240 ksiddomain_t tmpl; 241 242 mutex_enter(&sid_lock); 243 244 if (!sid_inited) { 245 avl_create(&sid_tree, ksid_cmp, sizeof (ksiddomain_t), 246 offsetof(ksiddomain_t, kd_link)); 247 248 res = ksid_enterdomain(dom); 249 sid_inited = B_TRUE; 250 mutex_exit(&sid_lock); 251 return (res); 252 } 253 254 tmpl.kd_name = (char *)dom; 255 256 res = avl_find(&sid_tree, &tmpl, NULL); 257 if (res == NULL) { 258 res = ksid_enterdomain(dom); 259 } else { 260 ksiddomain_hold(res); 261 } 262 263 mutex_exit(&sid_lock); 264 return (res); 265 } 266 267 const char * 268 ksid_getdomain(ksid_t *ks) 269 { 270 return (ks->ks_domain->kd_name); 271 } 272 273 uint_t 274 ksid_getrid(ksid_t *ks) 275 { 276 return (ks->ks_rid); 277 } 278 279 uid_t 280 ksid_getid(ksid_t *ks) 281 { 282 return (ks->ks_id); 283 } 284 285 #ifdef _KERNEL 286 int 287 ksid_lookupbyuid(zone_t *zone, uid_t id, ksid_t *res) 288 { 289 const char *sid_prefix; 290 291 if (kidmap_getsidbyuid(zone, id, &sid_prefix, &res->ks_rid) 292 != IDMAP_SUCCESS) 293 return (-1); 294 295 res->ks_domain = ksid_lookupdomain(sid_prefix); 296 297 res->ks_id = id; 298 299 return (0); 300 } 301 302 int 303 ksid_lookupbygid(zone_t *zone, gid_t id, ksid_t *res) 304 { 305 const char *sid_prefix; 306 307 if (kidmap_getsidbygid(zone, id, &sid_prefix, &res->ks_rid) 308 != IDMAP_SUCCESS) 309 return (-1); 310 311 res->ks_domain = ksid_lookupdomain(sid_prefix); 312 313 res->ks_id = id; 314 315 return (0); 316 } 317 #endif 318 319 credsid_t * 320 kcrsid_alloc(void) 321 { 322 credsid_t *kcr = kmem_zalloc(sizeof (*kcr), KM_SLEEP); 323 kcr->kr_ref = 1; 324 return (kcr); 325 } 326 327 /* 328 * Returns a credsid_t with a refcount of 1. 329 */ 330 static credsid_t * 331 kcrsid_dup(credsid_t *org) 332 { 333 credsid_t *new; 334 ksid_index_t ki; 335 336 if (org == NULL) 337 return (kcrsid_alloc()); 338 if (org->kr_ref == 1) 339 return (org); 340 new = kcrsid_alloc(); 341 342 /* Copy, then update reference counts */ 343 *new = *org; 344 new->kr_ref = 1; 345 for (ki = 0; ki < KSID_COUNT; ki++) 346 ksid_hold(&new->kr_sidx[ki]); 347 348 if (new->kr_sidlist != NULL) 349 ksidlist_hold(new->kr_sidlist); 350 351 kcrsid_rele(org); 352 return (new); 353 } 354 355 void 356 kcrsid_hold(credsid_t *kcr) 357 { 358 atomic_inc_32(&kcr->kr_ref); 359 } 360 361 void 362 kcrsid_rele(credsid_t *kcr) 363 { 364 if (atomic_dec_32_nv(&kcr->kr_ref) == 0) { 365 ksid_index_t i; 366 367 for (i = 0; i < KSID_COUNT; i++) 368 ksid_rele(&kcr->kr_sidx[i]); 369 370 if (kcr->kr_sidlist != NULL) 371 ksidlist_rele(kcr->kr_sidlist); 372 373 kmem_free(kcr, sizeof (*kcr)); 374 } 375 } 376 377 /* 378 * Copy the SID credential into a previously allocated piece of memory. 379 */ 380 void 381 kcrsidcopy_to(const credsid_t *okcr, credsid_t *nkcr) 382 { 383 int i; 384 385 ASSERT(nkcr->kr_ref == 1); 386 387 if (okcr == NULL) 388 return; 389 *nkcr = *okcr; 390 for (i = 0; i < KSID_COUNT; i++) 391 ksid_hold(&nkcr->kr_sidx[i]); 392 if (nkcr->kr_sidlist != NULL) 393 ksidlist_hold(nkcr->kr_sidlist); 394 nkcr->kr_ref = 1; 395 } 396 397 static int 398 kcrsid_sidcount(const credsid_t *kcr) 399 { 400 int cnt = 0; 401 int i; 402 403 if (kcr == NULL) 404 return (0); 405 406 for (i = 0; i < KSID_COUNT; i++) 407 if (kcr->kr_sidx[i].ks_domain != NULL) 408 cnt++; 409 410 if (kcr->kr_sidlist != NULL) 411 cnt += kcr->kr_sidlist->ksl_nsid; 412 return (cnt); 413 } 414 415 /* 416 * Argument needs to be a ksid_t with a properly held ks_domain reference. 417 */ 418 credsid_t * 419 kcrsid_setsid(credsid_t *okcr, ksid_t *ksp, ksid_index_t i) 420 { 421 int ocnt = kcrsid_sidcount(okcr); 422 credsid_t *nkcr; 423 424 /* 425 * Unset the particular ksid; if there are no other SIDs or if this 426 * is the last SID, remove the auxilary data structure. 427 */ 428 if (ksp == NULL) { 429 if (ocnt == 0 || 430 (ocnt == 1 && okcr->kr_sidx[i].ks_domain != NULL)) { 431 if (okcr != NULL) 432 kcrsid_rele(okcr); 433 return (NULL); 434 } 435 } 436 nkcr = kcrsid_dup(okcr); 437 ksid_rele(&nkcr->kr_sidx[i]); 438 if (ksp == NULL) 439 bzero(&nkcr->kr_sidx[i], sizeof (ksid_t)); 440 else 441 nkcr->kr_sidx[i] = *ksp; 442 443 return (nkcr); 444 } 445 446 static int 447 ksid_sid_cmp(const void *arg1, const void *arg2) 448 { 449 ksid_t *sid1 = (ksid_t *)arg1; 450 ksid_t *sid2 = (ksid_t *)arg2; 451 int cmp = AVL_CMP(sid1->ks_rid, sid2->ks_rid); 452 453 if (cmp == 0) 454 cmp = AVL_ISIGN(strcmp(ksid_getdomain(sid1), 455 ksid_getdomain(sid2))); 456 457 return (cmp); 458 } 459 460 static int 461 ksid_id_cmp(const void *arg1, const void *arg2) 462 { 463 ksid_t *sid1 = *(ksid_t **)arg1; 464 ksid_t *sid2 = *(ksid_t **)arg2; 465 466 return (AVL_CMP(sid1->ks_id, sid2->ks_id)); 467 } 468 469 /* 470 * Argument needs to be a ksidlist_t with properly held ks_domain references 471 * and a reference count taking the new reference into account. 472 */ 473 credsid_t * 474 kcrsid_setsidlist(credsid_t *okcr, ksidlist_t *ksl) 475 { 476 int ocnt = kcrsid_sidcount(okcr); 477 credsid_t *nkcr; 478 int i; 479 480 /* 481 * Unset the sidlist; if there are no further SIDs, remove the 482 * auxilary data structure. 483 */ 484 if (ksl == NULL) { 485 if (ocnt == 0 || (okcr->kr_sidlist != NULL && 486 ocnt == okcr->kr_sidlist->ksl_nsid)) { 487 if (okcr != NULL) 488 kcrsid_rele(okcr); 489 return (NULL); 490 } 491 } 492 /* This might return 'okcr' if the refcnt is 1 */ 493 nkcr = kcrsid_dup(okcr); 494 if (nkcr->kr_sidlist != NULL) 495 ksidlist_rele(nkcr->kr_sidlist); 496 497 nkcr->kr_sidlist = ksl; 498 499 /* sort the lists so that we can do binary search */ 500 if (ksl != NULL && ksl->ksl_sorted == NULL) { 501 qsort(ksl->ksl_sids, ksl->ksl_nsid, sizeof (ksid_t), 502 ksid_sid_cmp); 503 504 ksl->ksl_sorted = kmem_alloc(ksl->ksl_nsid * sizeof (ksid_t *), 505 KM_SLEEP); 506 for (i = 0; i < ksl->ksl_nsid; i++) 507 ksl->ksl_sorted[i] = &ksl->ksl_sids[i]; 508 qsort(ksl->ksl_sorted, ksl->ksl_nsid, sizeof (ksid_t *), 509 ksid_id_cmp); 510 } 511 512 return (nkcr); 513 } 514 515 ksidlist_t * 516 kcrsid_gidstosids(zone_t *zone, int ngrp, gid_t *grp) 517 { 518 int i; 519 ksidlist_t *list; 520 int cnt; 521 522 if (ngrp == 0) 523 return (NULL); 524 525 cnt = 0; 526 list = kmem_zalloc(KSIDLIST_MEM(ngrp), KM_SLEEP); 527 528 list->ksl_nsid = ngrp; 529 list->ksl_ref = 1; 530 531 for (i = 0; i < ngrp; i++) { 532 if (grp[i] > MAXUID) { 533 list->ksl_neid++; 534 #ifdef _KERNEL 535 if (ksid_lookupbygid(zone, 536 grp[i], &list->ksl_sids[i]) != 0) { 537 while (--i >= 0) 538 ksid_rele(&list->ksl_sids[i]); 539 cnt = 0; 540 break; 541 } 542 #endif 543 cnt++; 544 } else { 545 list->ksl_sids[i].ks_id = grp[i]; 546 } 547 } 548 if (cnt == 0) { 549 kmem_free(list, KSIDLIST_MEM(ngrp)); 550 return (NULL); 551 } 552 return (list); 553 } 554