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