xref: /freebsd/lib/libugidfw/ugidfw.c (revision f0adf7f5cdd241db2f2c817683191a6ef64a4e95)
1 /*-
2  * Copyright (c) 2002, 2004 Networks Associates Technology, Inc.
3  * All rights reserved.
4  *
5  * This software was developed for the FreeBSD Project by Network Associates
6  * Laboratories, the Security Research Division of Network Associates, Inc.
7  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8  * DARPA CHATS research program.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  * $FreeBSD$
32  */
33 #include <sys/param.h>
34 #include <sys/errno.h>
35 #include <sys/time.h>
36 #include <sys/sysctl.h>
37 #include <sys/vnode.h>
38 
39 #include <security/mac_bsdextended/mac_bsdextended.h>
40 
41 #include <grp.h>
42 #include <pwd.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 
47 #include "ugidfw.h"
48 
49 /*
50  * Text format for rules: rules contain subjectand object elements, mode.
51  * Each element takes the form "[not] [uid number] [gid number]".
52  * The total form is "subject [element] object [element] mode [mode]".
53  * At least * one of a uid or gid entry must be present; both may also be
54  * present.
55  */
56 
57 #define	MIB	"security.mac.bsdextended"
58 
59 int
60 bsde_rule_to_string(struct mac_bsdextended_rule *rule, char *buf, size_t buflen)
61 {
62 	struct group *grp;
63 	struct passwd *pwd;
64 	char *cur;
65 	size_t left, len;
66 	int anymode, unknownmode, truncated;
67 
68 	cur = buf;
69 	left = buflen;
70 	truncated = 0;
71 
72 	if (rule->mbr_subject.mbi_flags & (MBI_UID_DEFINED |
73 	    MBI_GID_DEFINED)) {
74 		len = snprintf(cur, left, "subject ");
75 		if (len < 0 || len > left)
76 			goto truncated;
77 		left -= len;
78 		cur += len;
79 
80 		if (rule->mbr_subject.mbi_flags & MBI_NEGATED) {
81 			len = snprintf(cur, left, "not ");
82 			if (len < 0 || len > left)
83 				goto truncated;
84 			left -= len;
85 			cur += len;
86 		}
87 		if (rule->mbr_subject.mbi_flags & MBI_UID_DEFINED) {
88 			pwd = getpwuid(rule->mbr_subject.mbi_uid);
89 			if (pwd != NULL) {
90 				len = snprintf(cur, left, "uid %s ",
91 				    pwd->pw_name);
92 				if (len < 0 || len > left)
93 					goto truncated;
94 				left -= len;
95 				cur += len;
96 			} else {
97 				len = snprintf(cur, left, "uid %u ",
98 				    rule->mbr_subject.mbi_uid);
99 				if (len < 0 || len > left)
100 					goto truncated;
101 				left -= len;
102 				cur += len;
103 			}
104 		}
105 		if (rule->mbr_subject.mbi_flags & MBI_GID_DEFINED) {
106 			grp = getgrgid(rule->mbr_subject.mbi_gid);
107 			if (grp != NULL) {
108 				len = snprintf(cur, left, "gid %s ",
109 				    grp->gr_name);
110 				if (len < 0 || len > left)
111 					goto truncated;
112 				left -= len;
113 				cur += len;
114 			} else {
115 				len = snprintf(cur, left, "gid %u ",
116 				    rule->mbr_subject.mbi_gid);
117 				if (len < 0 || len > left)
118 					goto truncated;
119 				left -= len;
120 				cur += len;
121 			}
122 		}
123 	}
124 	if (rule->mbr_object.mbi_flags & (MBI_UID_DEFINED |
125 	    MBI_GID_DEFINED)) {
126 		len = snprintf(cur, left, "object ");
127 		if (len < 0 || len > left)
128 			goto truncated;
129 		left -= len;
130 		cur += len;
131 
132 		if (rule->mbr_object.mbi_flags & MBI_NEGATED) {
133 			len = snprintf(cur, left, "not ");
134 			if (len < 0 || len > left)
135 				goto truncated;
136 			left -= len;
137 			cur += len;
138 		}
139 		if (rule->mbr_object.mbi_flags & MBI_UID_DEFINED) {
140 			pwd = getpwuid(rule->mbr_object.mbi_uid);
141 			if (pwd != NULL) {
142 				len = snprintf(cur, left, "uid %s ",
143 				    pwd->pw_name);
144 				if (len < 0 || len > left)
145 					goto truncated;
146 				left -= len;
147 				cur += len;
148 			} else {
149 				len = snprintf(cur, left, "uid %u ",
150 				    rule->mbr_object.mbi_uid);
151 				left -= len;
152 				cur += len;
153 			}
154 		}
155 		if (rule->mbr_object.mbi_flags & MBI_GID_DEFINED) {
156 			grp = getgrgid(rule->mbr_object.mbi_gid);
157 			if (grp != NULL) {
158 				len = snprintf(cur, left, "gid %s ",
159 				    grp->gr_name);
160 				if (len < 0 || len > left)
161 					goto truncated;
162 				left -= len;
163 				cur += len;
164 			} else {
165 				len = snprintf(cur, left, "gid %u ",
166 				    rule->mbr_object.mbi_gid);
167 				if (len < 0 || len > left)
168 					goto truncated;
169 				left -= len;
170 				cur += len;
171 			}
172 		}
173 	}
174 
175 	len = snprintf(cur, left, "mode ");
176 	if (len < 0 || len > left)
177 		goto truncated;
178 	left -= len;
179 	cur += len;
180 
181 	anymode = (rule->mbr_mode & VALLPERM);
182 	unknownmode = (rule->mbr_mode & ~VALLPERM);
183 
184 	if (rule->mbr_mode & VADMIN) {
185 		len = snprintf(cur, left, "a");
186 		if (len < 0 || len > left)
187 			goto truncated;
188 
189 		left -= len;
190 		cur += len;
191 	}
192 	if (rule->mbr_mode & VREAD) {
193 		len = snprintf(cur, left, "r");
194 		if (len < 0 || len > left)
195 			goto truncated;
196 
197 		left -= len;
198 		cur += len;
199 	}
200 	if (rule->mbr_mode & VSTAT) {
201 		len = snprintf(cur, left, "s");
202 		if (len < 0 || len > left)
203 			goto truncated;
204 
205 		left -= len;
206 		cur += len;
207 	}
208 	if (rule->mbr_mode & VWRITE) {
209 		len = snprintf(cur, left, "w");
210 		if (len < 0 || len > left)
211 			goto truncated;
212 
213 		left -= len;
214 		cur += len;
215 	}
216 	if (rule->mbr_mode & VEXEC) {
217 		len = snprintf(cur, left, "x");
218 		if (len < 0 || len > left)
219 			goto truncated;
220 
221 		left -= len;
222 		cur += len;
223 	}
224 	if (!anymode) {
225 		len = snprintf(cur, left, "n");
226 		if (len < 0 || len > left)
227 			goto truncated;
228 
229 		left -= len;
230 		cur += len;
231 	}
232 	if (unknownmode) {
233 		len = snprintf(cur, left, "?");
234 		if (len < 0 || len > left)
235 			goto truncated;
236 
237 		left -= len;
238 		cur += len;
239 	}
240 
241 	return (0);
242 
243 truncated:
244 	return (-1);
245 }
246 
247 int
248 bsde_parse_identity(int argc, char *argv[],
249     struct mac_bsdextended_identity *identity, size_t buflen, char *errstr)
250 {
251 	struct group *grp;
252 	struct passwd *pwd;
253 	int uid_seen, gid_seen, not_seen;
254 	int current;
255 	char *endp;
256 	long value;
257 	uid_t uid;
258 	gid_t gid;
259 	size_t len;
260 
261 	if (argc == 0) {
262 		len = snprintf(errstr, buflen, "Identity must not be empty");
263 		return (-1);
264 	}
265 
266 	current = 0;
267 
268 	/* First element might be "not". */
269 	if (strcmp("not", argv[0]) == 0) {
270 		not_seen = 1;
271 		current++;
272 	} else
273 		not_seen = 0;
274 
275 	if (current >= argc) {
276 		len = snprintf(errstr, buflen, "Identity short");
277 		return (-1);
278 	}
279 
280 	uid_seen = 0;
281 	uid = 0;
282 	gid_seen = 0;
283 	gid = 0;
284 
285 	/* First phrase: uid [uid] or gid[gid]. */
286 	if (strcmp("uid", argv[current]) == 0) {
287 		if (current + 2 > argc) {
288 			len = snprintf(errstr, buflen, "uid short");
289 			return (-1);
290 		}
291 		pwd = getpwnam(argv[current+1]);
292 		if (pwd != NULL)
293 			uid = pwd->pw_uid;
294 		else {
295 			value = strtol(argv[current+1], &endp, 10);
296 			if (*endp != '\0') {
297 				len = snprintf(errstr, buflen,
298 				    "invalid uid: '%s'",
299 				    argv[current+1]);
300 				return (-1);
301 			}
302 			uid = value;
303 		}
304 		uid_seen = 1;
305 		current += 2;
306 	} else if (strcmp("gid", argv[current]) == 0) {
307 		if (current + 2 > argc) {
308 			len = snprintf(errstr, buflen, "gid short");
309 			return (-1);
310 		}
311 		grp = getgrnam(argv[current+1]);
312 		if (grp != NULL)
313 			gid = grp->gr_gid;
314 		else {
315 			value = strtol(argv[current+1], &endp, 10);
316 			if (*endp != '\0') {
317 				len = snprintf(errstr, buflen,
318 				    "invalid gid: '%s'",
319 				    argv[current+1]);
320 				return (-1);
321 			}
322 			gid = value;
323 		}
324 		gid_seen = 1;
325 		current += 2;
326 	} else {
327 		len = snprintf(errstr, buflen, "'%s' not expected",
328 		    argv[current]);
329 		return (-1);
330 	}
331 
332 	/* Onto optional second phrase. */
333 	if (current + 1 < argc) {
334 		/* Second phrase: uid [uid] or gid [gid], but not a repeat. */
335 		if (strcmp("uid", argv[current]) == 0) {
336 			if (uid_seen) {
337 				len = snprintf(errstr, buflen,
338 				    "Only one uid permitted per identity clause");
339 				return (-1);
340 			}
341 			if (current + 2 > argc) {
342 				len = snprintf(errstr, buflen, "uid short");
343 				return (-1);
344 			}
345 			value = strtol(argv[current+1], &endp, 10);
346 			if (*endp != '\0') {
347 				len = snprintf(errstr, buflen, "invalid uid: '%s'",
348 				    argv[current+1]);
349 				return (-1);
350 			}
351 			uid = value;
352 			uid_seen = 1;
353 			current += 2;
354 		} else if (strcmp("gid", argv[current]) == 0) {
355 			if (gid_seen) {
356 				len = snprintf(errstr, buflen,
357 				    "Only one gid permitted per identity clause");
358 				return (-1);
359 			}
360 			if (current + 2 > argc) {
361 				len = snprintf(errstr, buflen, "gid short");
362 				return (-1);
363 			}
364 			value = strtol(argv[current+1], &endp, 10);
365 			if (*endp != '\0') {
366 				len = snprintf(errstr, buflen, "invalid gid: '%s'",
367 				    argv[current+1]);
368 				return (-1);
369 			}
370 			gid = value;
371 			gid_seen = 1;
372 			current += 2;
373 		} else {
374 			len = snprintf(errstr, buflen, "'%s' not expected",
375 			    argv[current]);
376 			return (-1);
377 		}
378 	}
379 
380 	if (current +1 < argc) {
381 		len = snprintf(errstr, buflen, "'%s' not expected",
382 		    argv[current]);
383 		return (-1);
384 	}
385 
386 	/* Fill out the identity. */
387 	identity->mbi_flags = 0;
388 
389 	if (not_seen)
390 		identity->mbi_flags |= MBI_NEGATED;
391 
392 	if (uid_seen) {
393 		identity->mbi_flags |= MBI_UID_DEFINED;
394 		identity->mbi_uid = uid;
395 	} else
396 		identity->mbi_uid = 0;
397 
398 	if (gid_seen) {
399 		identity->mbi_flags |= MBI_GID_DEFINED;
400 		identity->mbi_gid = gid;
401 	} else
402 		identity->mbi_gid = 0;
403 
404 	return (0);
405 }
406 
407 int
408 bsde_parse_mode(int argc, char *argv[], mode_t *mode, size_t buflen,
409     char *errstr)
410 {
411 	size_t len;
412 	int i;
413 
414 	if (argc == 0) {
415 		len = snprintf(errstr, buflen, "mode expects mode value");
416 		return (-1);
417 	}
418 
419 	if (argc != 1) {
420 		len = snprintf(errstr, buflen, "'%s' unexpected", argv[1]);
421 		return (-1);
422 	}
423 
424 	*mode = 0;
425 	for (i = 0; i < strlen(argv[0]); i++) {
426 		switch (argv[0][i]) {
427 		case 'a':
428 			*mode |= VADMIN;
429 			break;
430 		case 'r':
431 			*mode |= VREAD;
432 			break;
433 		case 's':
434 			*mode |= VSTAT;
435 			break;
436 		case 'w':
437 			*mode |= VWRITE;
438 			break;
439 		case 'x':
440 			*mode |= VEXEC;
441 			break;
442 		case 'n':
443 			/* ignore */
444 			break;
445 		default:
446 			len = snprintf(errstr, buflen, "Unknown mode letter: %c",
447 			    argv[0][i]);
448 			return (-1);
449 		}
450 	}
451 
452 	return (0);
453 }
454 
455 int
456 bsde_parse_rule(int argc, char *argv[], struct mac_bsdextended_rule *rule,
457     size_t buflen, char *errstr)
458 {
459 	int subject, subject_elements, subject_elements_length;
460 	int object, object_elements, object_elements_length;
461 	int mode, mode_elements, mode_elements_length;
462 	int error, i;
463 	size_t len;
464 
465 	bzero(rule, sizeof(*rule));
466 
467 	if (argc < 1) {
468 		len = snprintf(errstr, buflen, "Rule must begin with subject");
469 		return (-1);
470 	}
471 
472 	if (strcmp(argv[0], "subject") != 0) {
473 		len = snprintf(errstr, buflen, "Rule must begin with subject");
474 		return (-1);
475 	}
476 	subject = 0;
477 	subject_elements = 1;
478 
479 	/* Search forward for object. */
480 
481 	object = -1;
482 	for (i = 1; i < argc; i++)
483 		if (strcmp(argv[i], "object") == 0)
484 			object = i;
485 
486 	if (object == -1) {
487 		len = snprintf(errstr, buflen, "Rule must contain an object");
488 		return (-1);
489 	}
490 
491 	/* Search forward for mode. */
492 	mode = -1;
493 	for (i = object; i < argc; i++)
494 		if (strcmp(argv[i], "mode") == 0)
495 			mode = i;
496 
497 	if (mode == -1) {
498 		len = snprintf(errstr, buflen, "Rule must contain mode");
499 		return (-1);
500 	}
501 
502 	subject_elements_length = object - subject - 1;
503 	object_elements = object + 1;
504 	object_elements_length = mode - object_elements;
505 	mode_elements = mode + 1;
506 	mode_elements_length = argc - mode_elements;
507 
508 	error = bsde_parse_identity(subject_elements_length,
509 	    argv + subject_elements, &rule->mbr_subject, buflen, errstr);
510 	if (error)
511 		return (-1);
512 
513 	error = bsde_parse_identity(object_elements_length,
514 	    argv + object_elements, &rule->mbr_object, buflen, errstr);
515 	if (error)
516 		return (-1);
517 
518 	error = bsde_parse_mode(mode_elements_length, argv + mode_elements,
519 	    &rule->mbr_mode, buflen, errstr);
520 	if (error)
521 		return (-1);
522 
523 	return (0);
524 }
525 
526 int
527 bsde_parse_rule_string(const char *string, struct mac_bsdextended_rule *rule,
528     size_t buflen, char *errstr)
529 {
530 	char *stringdup, *stringp, *argv[20], **ap;
531 	int argc, error;
532 
533 	stringp = stringdup = strdup(string);
534 	while (*stringp == ' ' || *stringp == '\t')
535 		stringp++;
536 
537 	argc = 0;
538 	for (ap = argv; (*ap = strsep(&stringp, " \t")) != NULL;) {
539 		argc++;
540 		if (**ap != '\0')
541 			if (++ap >= &argv[20])
542 				break;
543 	}
544 
545 	error = bsde_parse_rule(argc, argv, rule, buflen, errstr);
546 
547 	free(stringdup);
548 
549 	return (error);
550 }
551 
552 int
553 bsde_get_mib(const char *string, int *name, size_t *namelen)
554 {
555 	size_t len;
556 	int error;
557 
558 	len = *namelen;
559 	error = sysctlnametomib(string, name, &len);
560 	if (error)
561 		return (error);
562 
563 	*namelen = len;
564 	return (0);
565 }
566 
567 int
568 bsde_get_rule_count(size_t buflen, char *errstr)
569 {
570 	size_t len;
571 	int error;
572 	int rule_count;
573 
574 	len = sizeof(rule_count);
575 	error = sysctlbyname(MIB ".rule_count", &rule_count, &len, NULL, 0);
576 	if (error) {
577 		len = snprintf(errstr, buflen, strerror(errno));
578 		return (-1);
579 	}
580 	if (len != sizeof(rule_count)) {
581 		len = snprintf(errstr, buflen, "Data error in %s.rule_count",
582 		    MIB);
583 		return (-1);
584 	}
585 
586 	return (rule_count);
587 }
588 
589 int
590 bsde_get_rule_slots(size_t buflen, char *errstr)
591 {
592 	size_t len;
593 	int error;
594 	int rule_slots;
595 
596 	len = sizeof(rule_slots);
597 	error = sysctlbyname(MIB ".rule_slots", &rule_slots, &len, NULL, 0);
598 	if (error) {
599 		len = snprintf(errstr, buflen, strerror(errno));
600 		return (-1);
601 	}
602 	if (len != sizeof(rule_slots)) {
603 		len = snprintf(errstr, buflen, "Data error in %s.rule_slots",
604 		    MIB);
605 		return (-1);
606 	}
607 
608 	return (rule_slots);
609 }
610 
611 /*
612  * Returns 0 for success;
613  * Returns -1 for failure;
614  * Returns -2 for not present
615  */
616 int
617 bsde_get_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t errlen,
618     char *errstr)
619 {
620 	int name[10];
621 	size_t len, size;
622 	int error;
623 
624 	len = 10;
625 	error = bsde_get_mib(MIB ".rules", name, &len);
626 	if (error) {
627 		len = snprintf(errstr, errlen, "%s: %s", MIB ".rules",
628 		    strerror(errno));
629 		return (-1);
630 	}
631 
632 	size = sizeof(*rule);
633 	name[len] = rulenum;
634 	len++;
635 	error = sysctl(name, len, rule, &size, NULL, 0);
636 	if (error  == -1 && errno == ENOENT)
637 		return (-2);
638 	if (error) {
639 		len = snprintf(errstr, errlen, "%s.%d: %s", MIB ".rules",
640 		    rulenum, strerror(errno));
641 		return (-1);
642 	} else if (size != sizeof(*rule)) {
643 		len = snprintf(errstr, errlen, "Data error in %s.%d: %s",
644 		    MIB ".rules", rulenum, strerror(errno));
645 		return (-1);
646 	}
647 
648 	return (0);
649 }
650 
651 int
652 bsde_delete_rule(int rulenum, size_t buflen, char *errstr)
653 {
654 	struct mac_bsdextended_rule rule;
655 	int name[10];
656 	size_t len, size;
657 	int error;
658 
659 	len = 10;
660 	error = bsde_get_mib(MIB ".rules", name, &len);
661 	if (error) {
662 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
663 		    strerror(errno));
664 		return (-1);
665 	}
666 
667 	name[len] = rulenum;
668 	len++;
669 
670 	size = sizeof(rule);
671 	error = sysctl(name, len, NULL, NULL, &rule, 0);
672 	if (error) {
673 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
674 		    rulenum, strerror(errno));
675 		return (-1);
676 	}
677 
678 	return (0);
679 }
680 
681 int
682 bsde_set_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
683     char *errstr)
684 {
685 	int name[10];
686 	size_t len, size;
687 	int error;
688 
689 	len = 10;
690 	error = bsde_get_mib(MIB ".rules", name, &len);
691 	if (error) {
692 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
693 		    strerror(errno));
694 		return (-1);
695 	}
696 
697 	name[len] = rulenum;
698 	len++;
699 
700 	size = sizeof(*rule);
701 	error = sysctl(name, len, NULL, NULL, rule, size);
702 	if (error) {
703 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
704 		    rulenum, strerror(errno));
705 		return (-1);
706 	}
707 
708 	return (0);
709 }
710 
711 int
712 bsde_add_rule(int *rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
713     char *errstr)
714 {
715 	char charstr[BUFSIZ];
716 	int name[10];
717 	size_t len, size;
718 	int error, rule_slots;
719 
720 	len = 10;
721 	error = bsde_get_mib(MIB ".rules", name, &len);
722 	if (error) {
723 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
724 		    strerror(errno));
725 		return (-1);
726 	}
727 
728 	rule_slots = bsde_get_rule_slots(BUFSIZ, charstr);
729 	if (rule_slots == -1) {
730 		len = snprintf(errstr, buflen, "unable to get rule slots: %s",
731 		    strerror(errno));
732 		return (-1);
733 	}
734 
735 	name[len] = rule_slots;
736 	len++;
737 
738 	size = sizeof(*rule);
739 	error = sysctl(name, len, NULL, NULL, rule, size);
740 	if (error) {
741 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
742 		    rule_slots, strerror(errno));
743 		return (-1);
744 	}
745 
746 	if (rulenum != NULL)
747 		rule_slots;
748 
749 	return (0);
750 }
751