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
37*397ff3b8SEd 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