1 /*- 2 * Copyright (c) 2010 The FreeBSD Foundation 3 * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net> 4 * All rights reserved. 5 * 6 * This software was developed by Pawel Jakub Dawidek under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/capability.h> 35 #include <sys/param.h> 36 #include <sys/disk.h> 37 #include <sys/ioctl.h> 38 #include <sys/jail.h> 39 #include <sys/stat.h> 40 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <pwd.h> 44 #include <stdarg.h> 45 #include <stdbool.h> 46 #include <stdio.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include <pjdlog.h> 51 52 #include "hast.h" 53 #include "subr.h" 54 55 int 56 vsnprlcat(char *str, size_t size, const char *fmt, va_list ap) 57 { 58 size_t len; 59 60 len = strlen(str); 61 return (vsnprintf(str + len, size - len, fmt, ap)); 62 } 63 64 int 65 snprlcat(char *str, size_t size, const char *fmt, ...) 66 { 67 va_list ap; 68 int result; 69 70 va_start(ap, fmt); 71 result = vsnprlcat(str, size, fmt, ap); 72 va_end(ap); 73 return (result); 74 } 75 76 int 77 provinfo(struct hast_resource *res, bool dowrite) 78 { 79 struct stat sb; 80 81 PJDLOG_ASSERT(res->hr_localpath != NULL && 82 res->hr_localpath[0] != '\0'); 83 84 if (res->hr_localfd == -1) { 85 res->hr_localfd = open(res->hr_localpath, 86 dowrite ? O_RDWR : O_RDONLY); 87 if (res->hr_localfd < 0) { 88 KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to open %s", 89 res->hr_localpath)); 90 return (-1); 91 } 92 } 93 if (fstat(res->hr_localfd, &sb) < 0) { 94 KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to stat %s", 95 res->hr_localpath)); 96 return (-1); 97 } 98 if (S_ISCHR(sb.st_mode)) { 99 /* 100 * If this is character device, it is most likely GEOM provider. 101 */ 102 if (ioctl(res->hr_localfd, DIOCGMEDIASIZE, 103 &res->hr_local_mediasize) < 0) { 104 KEEP_ERRNO(pjdlog_errno(LOG_ERR, 105 "Unable obtain provider %s mediasize", 106 res->hr_localpath)); 107 return (-1); 108 } 109 if (ioctl(res->hr_localfd, DIOCGSECTORSIZE, 110 &res->hr_local_sectorsize) < 0) { 111 KEEP_ERRNO(pjdlog_errno(LOG_ERR, 112 "Unable obtain provider %s sectorsize", 113 res->hr_localpath)); 114 return (-1); 115 } 116 } else if (S_ISREG(sb.st_mode)) { 117 /* 118 * We also support regular files for which we hardcode 119 * sector size of 512 bytes. 120 */ 121 res->hr_local_mediasize = sb.st_size; 122 res->hr_local_sectorsize = 512; 123 } else { 124 /* 125 * We support no other file types. 126 */ 127 pjdlog_error("%s is neither GEOM provider nor regular file.", 128 res->hr_localpath); 129 errno = EFTYPE; 130 return (-1); 131 } 132 return (0); 133 } 134 135 const char * 136 role2str(int role) 137 { 138 139 switch (role) { 140 case HAST_ROLE_INIT: 141 return ("init"); 142 case HAST_ROLE_PRIMARY: 143 return ("primary"); 144 case HAST_ROLE_SECONDARY: 145 return ("secondary"); 146 } 147 return ("unknown"); 148 } 149 150 int 151 drop_privs(struct hast_resource *res) 152 { 153 char jailhost[sizeof(res->hr_name) * 2]; 154 struct jail jailst; 155 struct passwd *pw; 156 uid_t ruid, euid, suid; 157 gid_t rgid, egid, sgid; 158 gid_t gidset[1]; 159 bool capsicum, jailed; 160 161 /* 162 * According to getpwnam(3) we have to clear errno before calling the 163 * function to be able to distinguish between an error and missing 164 * entry (with is not treated as error by getpwnam(3)). 165 */ 166 errno = 0; 167 pw = getpwnam(HAST_USER); 168 if (pw == NULL) { 169 if (errno != 0) { 170 KEEP_ERRNO(pjdlog_errno(LOG_ERR, 171 "Unable to find info about '%s' user", HAST_USER)); 172 return (-1); 173 } else { 174 pjdlog_error("'%s' user doesn't exist.", HAST_USER); 175 errno = ENOENT; 176 return (-1); 177 } 178 } 179 180 bzero(&jailst, sizeof(jailst)); 181 jailst.version = JAIL_API_VERSION; 182 jailst.path = pw->pw_dir; 183 if (res == NULL) { 184 (void)snprintf(jailhost, sizeof(jailhost), "hastctl"); 185 } else { 186 (void)snprintf(jailhost, sizeof(jailhost), "hastd: %s (%s)", 187 res->hr_name, role2str(res->hr_role)); 188 } 189 jailst.hostname = jailhost; 190 jailst.jailname = NULL; 191 jailst.ip4s = 0; 192 jailst.ip4 = NULL; 193 jailst.ip6s = 0; 194 jailst.ip6 = NULL; 195 if (jail(&jailst) >= 0) { 196 jailed = true; 197 } else { 198 jailed = false; 199 pjdlog_errno(LOG_WARNING, 200 "Unable to jail to directory to %s", pw->pw_dir); 201 if (chroot(pw->pw_dir) == -1) { 202 KEEP_ERRNO(pjdlog_errno(LOG_ERR, 203 "Unable to change root directory to %s", 204 pw->pw_dir)); 205 return (-1); 206 } 207 } 208 PJDLOG_VERIFY(chdir("/") == 0); 209 gidset[0] = pw->pw_gid; 210 if (setgroups(1, gidset) == -1) { 211 KEEP_ERRNO(pjdlog_errno(LOG_ERR, 212 "Unable to set groups to gid %u", 213 (unsigned int)pw->pw_gid)); 214 return (-1); 215 } 216 if (setgid(pw->pw_gid) == -1) { 217 KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to set gid to %u", 218 (unsigned int)pw->pw_gid)); 219 return (-1); 220 } 221 if (setuid(pw->pw_uid) == -1) { 222 KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to set uid to %u", 223 (unsigned int)pw->pw_uid)); 224 return (-1); 225 } 226 227 /* 228 * Until capsicum doesn't allow ioctl(2) we cannot use it to sandbox 229 * primary and secondary worker processes, as primary uses GGATE 230 * ioctls and secondary uses ioctls to handle BIO_DELETE and BIO_FLUSH. 231 * For now capsicum is only used to sandbox hastctl. 232 */ 233 if (res == NULL) 234 capsicum = (cap_enter() == 0); 235 else 236 capsicum = false; 237 238 /* 239 * Better be sure that everything succeeded. 240 */ 241 PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0); 242 PJDLOG_VERIFY(ruid == pw->pw_uid); 243 PJDLOG_VERIFY(euid == pw->pw_uid); 244 PJDLOG_VERIFY(suid == pw->pw_uid); 245 PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0); 246 PJDLOG_VERIFY(rgid == pw->pw_gid); 247 PJDLOG_VERIFY(egid == pw->pw_gid); 248 PJDLOG_VERIFY(sgid == pw->pw_gid); 249 PJDLOG_VERIFY(getgroups(0, NULL) == 1); 250 PJDLOG_VERIFY(getgroups(1, gidset) == 1); 251 PJDLOG_VERIFY(gidset[0] == pw->pw_gid); 252 253 pjdlog_debug(1, 254 "Privileges successfully dropped using %s%s+setgid+setuid.", 255 capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot"); 256 257 return (0); 258 } 259