xref: /freebsd/contrib/openbsm/bin/auditdistd/sandbox.c (revision b1f9167f94059fd55c630891d359bcff987bd7eb)
1 /*-
2  * Copyright (c) 2012 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $P4: //depot/projects/trustedbsd/openbsm/bin/auditdistd/sandbox.c#3 $
30  */
31 
32 #include <config/config.h>
33 
34 #include <sys/param.h>
35 #ifdef HAVE_JAIL
36 #include <sys/jail.h>
37 #endif
38 #ifdef HAVE_CAP_ENTER
39 #include <sys/capability.h>
40 #endif
41 
42 #include <errno.h>
43 #include <pwd.h>
44 #include <stdarg.h>
45 #include <stdbool.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <strings.h>
49 #include <unistd.h>
50 
51 #include "pjdlog.h"
52 #include "sandbox.h"
53 
54 static int
55 groups_compare(const void *grp0, const void *grp1)
56 {
57 	gid_t gr0 = *(const gid_t *)grp0;
58 	gid_t gr1 = *(const gid_t *)grp1;
59 
60 	return (gr0 <= gr1 ? (gr0 < gr1 ? -1 : 0) : 1);
61 
62 }
63 
64 int
65 sandbox(const char *user, bool capsicum, const char *fmt, ...)
66 {
67 #ifdef HAVE_JAIL
68 	struct jail jailst;
69 	char *jailhost;
70 	va_list ap;
71 #endif
72 	struct passwd *pw;
73 	uid_t ruid, euid;
74 	gid_t rgid, egid;
75 #ifdef HAVE_GETRESUID
76 	uid_t suid;
77 #endif
78 #ifdef HAVE_GETRESGID
79 	gid_t sgid;
80 #endif
81 	gid_t *groups, *ggroups;
82 	bool jailed;
83 	int ngroups, ret;
84 
85 	PJDLOG_ASSERT(user != NULL);
86 	PJDLOG_ASSERT(fmt != NULL);
87 
88 	ret = -1;
89 	groups = NULL;
90 	ggroups = NULL;
91 
92 	/*
93 	 * According to getpwnam(3) we have to clear errno before calling the
94 	 * function to be able to distinguish between an error and missing
95 	 * entry (with is not treated as error by getpwnam(3)).
96 	 */
97 	errno = 0;
98 	pw = getpwnam(user);
99 	if (pw == NULL) {
100 		if (errno != 0) {
101 			pjdlog_errno(LOG_ERR,
102 			    "Unable to find info about '%s' user", user);
103 			goto out;
104 		} else {
105 			pjdlog_error("'%s' user doesn't exist.", user);
106 			errno = ENOENT;
107 			goto out;
108 		}
109 	}
110 
111 	ngroups = sysconf(_SC_NGROUPS_MAX);
112 	if (ngroups == -1) {
113 		pjdlog_errno(LOG_WARNING,
114 		    "Unable to obtain maximum number of groups");
115 		ngroups = NGROUPS_MAX;
116 	}
117 	ngroups++;	/* For base gid. */
118 	groups = malloc(sizeof(groups[0]) * ngroups);
119 	if (groups == NULL) {
120 		pjdlog_error("Unable to allocate memory for %d groups.",
121 		    ngroups);
122 		goto out;
123 	}
124 	if (getgrouplist(user, pw->pw_gid, groups, &ngroups) == -1) {
125 		pjdlog_error("Unable to obtain groups of user %s.", user);
126 		goto out;
127 	}
128 
129 #ifdef HAVE_JAIL
130 	va_start(ap, fmt);
131 	(void)vasprintf(&jailhost, fmt, ap);
132 	va_end(ap);
133 	if (jailhost == NULL) {
134 		pjdlog_error("Unable to allocate memory for jail host name.");
135 		goto out;
136 	}
137 	bzero(&jailst, sizeof(jailst));
138 	jailst.version = JAIL_API_VERSION;
139 	jailst.path = pw->pw_dir;
140 	jailst.hostname = jailhost;
141 	if (jail(&jailst) >= 0) {
142 		jailed = true;
143 	} else {
144 		jailed = false;
145 		pjdlog_errno(LOG_WARNING,
146 		    "Unable to jail to directory %s", pw->pw_dir);
147 	}
148 	free(jailhost);
149 #else	/* !HAVE_JAIL */
150 	jailed = false;
151 #endif	/* !HAVE_JAIL */
152 
153 	if (!jailed) {
154 		if (chroot(pw->pw_dir) == -1) {
155 			pjdlog_errno(LOG_ERR,
156 			    "Unable to change root directory to %s",
157 			    pw->pw_dir);
158 			goto out;
159 		}
160 	}
161 	PJDLOG_VERIFY(chdir("/") == 0);
162 
163 	if (setgroups(ngroups, groups) == -1) {
164 		pjdlog_errno(LOG_ERR, "Unable to set groups");
165 		goto out;
166 	}
167 	if (setgid(pw->pw_gid) == -1) {
168 		pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
169 		    (unsigned int)pw->pw_gid);
170 		goto out;
171 	}
172 	if (setuid(pw->pw_uid) == -1) {
173 		pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
174 		    (unsigned int)pw->pw_uid);
175 		goto out;
176 	}
177 
178 #ifdef HAVE_CAP_ENTER
179 	if (capsicum) {
180 		capsicum = (cap_enter() == 0);
181 		if (!capsicum) {
182 			pjdlog_common(LOG_DEBUG, 1, errno,
183 			    "Unable to sandbox using capsicum");
184 		}
185 	}
186 #else	/* !HAVE_CAP_ENTER */
187 	capsicum = false;
188 #endif	/* !HAVE_CAP_ENTER */
189 
190 	/*
191 	 * Better be sure that everything succeeded.
192 	 */
193 #ifdef HAVE_GETRESUID
194 	PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
195 	PJDLOG_VERIFY(suid == pw->pw_uid);
196 #else
197 	ruid = getuid();
198 	euid = geteuid();
199 #endif
200 	PJDLOG_VERIFY(ruid == pw->pw_uid);
201 	PJDLOG_VERIFY(euid == pw->pw_uid);
202 #ifdef HAVE_GETRESGID
203 	PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
204 	PJDLOG_VERIFY(sgid == pw->pw_gid);
205 #else
206 	rgid = getgid();
207 	egid = getegid();
208 #endif
209 	PJDLOG_VERIFY(rgid == pw->pw_gid);
210 	PJDLOG_VERIFY(egid == pw->pw_gid);
211 	PJDLOG_VERIFY(getgroups(0, NULL) == ngroups);
212 	ggroups = malloc(sizeof(ggroups[0]) * ngroups);
213 	if (ggroups == NULL) {
214 		pjdlog_error("Unable to allocate memory for %d groups.",
215 		    ngroups);
216 		goto out;
217 	}
218 	PJDLOG_VERIFY(getgroups(ngroups, ggroups) == ngroups);
219 	qsort(groups, (size_t)ngroups, sizeof(groups[0]), groups_compare);
220 	qsort(ggroups, (size_t)ngroups, sizeof(ggroups[0]), groups_compare);
221 	PJDLOG_VERIFY(bcmp(groups, ggroups, sizeof(groups[0]) * ngroups) == 0);
222 
223 	pjdlog_debug(1,
224 	    "Privileges successfully dropped using %s%s+setgid+setuid.",
225 	    capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot");
226 
227 	ret = 0;
228 out:
229 	if (groups != NULL)
230 		free(groups);
231 	if (ggroups != NULL)
232 		free(ggroups);
233 	return (ret);
234 }
235