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