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
54static int
55groups_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
64int
65sandbox(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;
228out:
229	if (groups != NULL)
230		free(groups);
231	if (ggroups != NULL)
232		free(ggroups);
233	return (ret);
234}
235