xref: /illumos-gate/usr/src/uts/common/os/sid.c (revision 5e96687e18c05c76765c7d6920cd0b6505f2d587)
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