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/capsicum.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
groups_compare(const void * grp0,const void * grp1)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
sandbox(const char * user,bool capsicum,const char * fmt,...)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