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
ksid_enterdomain(const char * dom)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
ksid_hold(ksid_t * ks)76 ksid_hold(ksid_t *ks)
77 {
78 if (ks->ks_domain != NULL)
79 ksiddomain_hold(ks->ks_domain);
80 }
81
82 void
ksid_rele(ksid_t * ks)83 ksid_rele(ksid_t *ks)
84 {
85 if (ks->ks_domain != NULL)
86 ksiddomain_rele(ks->ks_domain);
87 }
88
89 void
ksiddomain_hold(ksiddomain_t * kd)90 ksiddomain_hold(ksiddomain_t *kd)
91 {
92 atomic_inc_32(&kd->kd_ref);
93 }
94
95 void
ksiddomain_rele(ksiddomain_t * kd)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
ksidlist_hold(ksidlist_t * ksl)115 ksidlist_hold(ksidlist_t *ksl)
116 {
117 atomic_inc_32(&ksl->ksl_ref);
118 }
119
120 void
ksidlist_rele(ksidlist_t * ksl)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
ksidlist_has_sid(ksidlist_t * ksl,const char * domain,uint32_t rid)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
ksidlist_has_pid(ksidlist_t * ksl,uint32_t pid)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
ksid_cmp(const void * a,const void * b)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
ksid_lookupdomain(const char * dom)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 *
ksid_getdomain(ksid_t * ks)268 ksid_getdomain(ksid_t *ks)
269 {
270 return (ks->ks_domain->kd_name);
271 }
272
273 uint_t
ksid_getrid(ksid_t * ks)274 ksid_getrid(ksid_t *ks)
275 {
276 return (ks->ks_rid);
277 }
278
279 uid_t
ksid_getid(ksid_t * ks)280 ksid_getid(ksid_t *ks)
281 {
282 return (ks->ks_id);
283 }
284
285 #ifdef _KERNEL
286 int
ksid_lookupbyuid(zone_t * zone,uid_t id,ksid_t * res)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
ksid_lookupbygid(zone_t * zone,gid_t id,ksid_t * res)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 *
kcrsid_alloc(void)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 *
kcrsid_dup(credsid_t * org)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
kcrsid_hold(credsid_t * kcr)356 kcrsid_hold(credsid_t *kcr)
357 {
358 atomic_inc_32(&kcr->kr_ref);
359 }
360
361 void
kcrsid_rele(credsid_t * kcr)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
kcrsidcopy_to(const credsid_t * okcr,credsid_t * nkcr)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
kcrsid_sidcount(const credsid_t * kcr)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 *
kcrsid_setsid(credsid_t * okcr,ksid_t * ksp,ksid_index_t i)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
ksid_sid_cmp(const void * arg1,const void * arg2)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
ksid_id_cmp(const void * arg1,const void * arg2)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 *
kcrsid_setsidlist(credsid_t * okcr,ksidlist_t * ksl)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 *
kcrsid_gidstosids(zone_t * zone,int ngrp,gid_t * grp)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