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