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
52static int
53groups_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
62int
63sandbox(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;
226out:
227	if (groups != NULL)
228		free(groups);
229	if (ggroups != NULL)
230		free(ggroups);
231	return (ret);
232}
233