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 
30 #include <config/config.h>
31 
32 #include <sys/param.h>
33 #ifdef HAVE_JAIL
34 #include <sys/jail.h>
35 #endif
36 #ifdef HAVE_CAP_ENTER
37 #include <sys/capability.h>
38 #endif
39 
40 #include <errno.h>
41 #include <pwd.h>
42 #include <stdarg.h>
43 #include <stdbool.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <strings.h>
47 #include <unistd.h>
48 
49 #include "pjdlog.h"
50 #include "sandbox.h"
51 
52 static int
53 groups_compare(const void *grp0, const void *grp1)
54 {
55 	gid_t gr0 = *(const gid_t *)grp0;
56 	gid_t gr1 = *(const gid_t *)grp1;
57 
58 	return (gr0 <= gr1 ? (gr0 < gr1 ? -1 : 0) : 1);
59 
60 }
61 
62 int
63 sandbox(const char *user, bool capsicum, const char *fmt, ...)
64 {
65 #ifdef HAVE_JAIL
66 	struct jail jailst;
67 	char *jailhost;
68 	va_list ap;
69 #endif
70 	struct passwd *pw;
71 	uid_t ruid, euid;
72 	gid_t rgid, egid;
73 #ifdef HAVE_GETRESUID
74 	uid_t suid;
75 #endif
76 #ifdef HAVE_GETRESGID
77 	gid_t sgid;
78 #endif
79 	gid_t *groups, *ggroups;
80 	bool jailed;
81 	int ngroups, ret;
82 
83 	PJDLOG_ASSERT(user != NULL);
84 	PJDLOG_ASSERT(fmt != NULL);
85 
86 	ret = -1;
87 	groups = NULL;
88 	ggroups = NULL;
89 
90 	/*
91 	 * According to getpwnam(3) we have to clear errno before calling the
92 	 * function to be able to distinguish between an error and missing
93 	 * entry (with is not treated as error by getpwnam(3)).
94 	 */
95 	errno = 0;
96 	pw = getpwnam(user);
97 	if (pw == NULL) {
98 		if (errno != 0) {
99 			pjdlog_errno(LOG_ERR,
100 			    "Unable to find info about '%s' user", user);
101 			goto out;
102 		} else {
103 			pjdlog_error("'%s' user doesn't exist.", user);
104 			errno = ENOENT;
105 			goto out;
106 		}
107 	}
108 
109 	ngroups = sysconf(_SC_NGROUPS_MAX);
110 	if (ngroups == -1) {
111 		pjdlog_errno(LOG_WARNING,
112 		    "Unable to obtain maximum number of groups");
113 		ngroups = NGROUPS_MAX;
114 	}
115 	ngroups++;	/* For base gid. */
116 	groups = malloc(sizeof(groups[0]) * ngroups);
117 	if (groups == NULL) {
118 		pjdlog_error("Unable to allocate memory for %d groups.",
119 		    ngroups);
120 		goto out;
121 	}
122 	if (getgrouplist(user, pw->pw_gid, groups, &ngroups) == -1) {
123 		pjdlog_error("Unable to obtain groups of user %s.", user);
124 		goto out;
125 	}
126 
127 #ifdef HAVE_JAIL
128 	va_start(ap, fmt);
129 	(void)vasprintf(&jailhost, fmt, ap);
130 	va_end(ap);
131 	if (jailhost == NULL) {
132 		pjdlog_error("Unable to allocate memory for jail host name.");
133 		goto out;
134 	}
135 	bzero(&jailst, sizeof(jailst));
136 	jailst.version = JAIL_API_VERSION;
137 	jailst.path = pw->pw_dir;
138 	jailst.hostname = jailhost;
139 	if (jail(&jailst) >= 0) {
140 		jailed = true;
141 	} else {
142 		jailed = false;
143 		pjdlog_errno(LOG_WARNING,
144 		    "Unable to jail to directory %s", pw->pw_dir);
145 	}
146 	free(jailhost);
147 #else	/* !HAVE_JAIL */
148 	jailed = false;
149 #endif	/* !HAVE_JAIL */
150 
151 	if (!jailed) {
152 		if (chroot(pw->pw_dir) == -1) {
153 			pjdlog_errno(LOG_ERR,
154 			    "Unable to change root directory to %s",
155 			    pw->pw_dir);
156 			goto out;
157 		}
158 	}
159 	PJDLOG_VERIFY(chdir("/") == 0);
160 
161 	if (setgroups(ngroups, groups) == -1) {
162 		pjdlog_errno(LOG_ERR, "Unable to set groups");
163 		goto out;
164 	}
165 	if (setgid(pw->pw_gid) == -1) {
166 		pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
167 		    (unsigned int)pw->pw_gid);
168 		goto out;
169 	}
170 	if (setuid(pw->pw_uid) == -1) {
171 		pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
172 		    (unsigned int)pw->pw_uid);
173 		goto out;
174 	}
175 
176 #ifdef HAVE_CAP_ENTER
177 	if (capsicum) {
178 		capsicum = (cap_enter() == 0);
179 		if (!capsicum) {
180 			pjdlog_common(LOG_DEBUG, 1, errno,
181 			    "Unable to sandbox using capsicum");
182 		}
183 	}
184 #else	/* !HAVE_CAP_ENTER */
185 	capsicum = false;
186 #endif	/* !HAVE_CAP_ENTER */
187 
188 	/*
189 	 * Better be sure that everything succeeded.
190 	 */
191 #ifdef HAVE_GETRESUID
192 	PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
193 	PJDLOG_VERIFY(suid == pw->pw_uid);
194 #else
195 	ruid = getuid();
196 	euid = geteuid();
197 #endif
198 	PJDLOG_VERIFY(ruid == pw->pw_uid);
199 	PJDLOG_VERIFY(euid == pw->pw_uid);
200 #ifdef HAVE_GETRESGID
201 	PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
202 	PJDLOG_VERIFY(sgid == pw->pw_gid);
203 #else
204 	rgid = getgid();
205 	egid = getegid();
206 #endif
207 	PJDLOG_VERIFY(rgid == pw->pw_gid);
208 	PJDLOG_VERIFY(egid == pw->pw_gid);
209 	PJDLOG_VERIFY(getgroups(0, NULL) == ngroups);
210 	ggroups = malloc(sizeof(ggroups[0]) * ngroups);
211 	if (ggroups == NULL) {
212 		pjdlog_error("Unable to allocate memory for %d groups.",
213 		    ngroups);
214 		goto out;
215 	}
216 	PJDLOG_VERIFY(getgroups(ngroups, ggroups) == ngroups);
217 	qsort(groups, (size_t)ngroups, sizeof(groups[0]), groups_compare);
218 	qsort(ggroups, (size_t)ngroups, sizeof(ggroups[0]), groups_compare);
219 	PJDLOG_VERIFY(bcmp(groups, ggroups, sizeof(groups[0]) * ngroups) == 0);
220 
221 	pjdlog_debug(1,
222 	    "Privileges successfully dropped using %s%s+setgid+setuid.",
223 	    capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot");
224 
225 	ret = 0;
226 out:
227 	if (groups != NULL)
228 		free(groups);
229 	if (ggroups != NULL)
230 		free(ggroups);
231 	return (ret);
232 }
233