1204076Spjd/*- 2204076Spjd * Copyright (c) 2010 The FreeBSD Foundation 3219887Spjd * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net> 4204076Spjd * All rights reserved. 5204076Spjd * 6204076Spjd * This software was developed by Pawel Jakub Dawidek under sponsorship from 7204076Spjd * the FreeBSD Foundation. 8204076Spjd * 9204076Spjd * Redistribution and use in source and binary forms, with or without 10204076Spjd * modification, are permitted provided that the following conditions 11204076Spjd * are met: 12204076Spjd * 1. Redistributions of source code must retain the above copyright 13204076Spjd * notice, this list of conditions and the following disclaimer. 14204076Spjd * 2. Redistributions in binary form must reproduce the above copyright 15204076Spjd * notice, this list of conditions and the following disclaimer in the 16204076Spjd * documentation and/or other materials provided with the distribution. 17204076Spjd * 18204076Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19204076Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20204076Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21204076Spjd * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22204076Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23204076Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24204076Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25204076Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26204076Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27204076Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28204076Spjd * SUCH DAMAGE. 29204076Spjd */ 30204076Spjd 31204076Spjd#include <sys/cdefs.h> 32204076Spjd__FBSDID("$FreeBSD: releng/11.0/sbin/hastd/subr.c 298317 2016-04-20 00:55:35Z araujo $"); 33204076Spjd 34221899Spjd#include <sys/param.h> 35204076Spjd#include <sys/disk.h> 36204076Spjd#include <sys/ioctl.h> 37221899Spjd#include <sys/jail.h> 38204076Spjd#include <sys/stat.h> 39248297Spjd#ifdef HAVE_CAPSICUM 40263234Srwatson#include <sys/capsicum.h> 41248297Spjd#include <geom/gate/g_gate.h> 42248297Spjd#endif 43204076Spjd 44204076Spjd#include <errno.h> 45204076Spjd#include <fcntl.h> 46218048Spjd#include <pwd.h> 47219815Spjd#include <stdarg.h> 48219847Spjd#include <stdbool.h> 49219815Spjd#include <stdio.h> 50219815Spjd#include <string.h> 51218048Spjd#include <unistd.h> 52204076Spjd 53204076Spjd#include <pjdlog.h> 54204076Spjd 55204076Spjd#include "hast.h" 56204076Spjd#include "subr.h" 57204076Spjd 58204076Spjdint 59219815Spjdvsnprlcat(char *str, size_t size, const char *fmt, va_list ap) 60219815Spjd{ 61219815Spjd size_t len; 62219815Spjd 63219815Spjd len = strlen(str); 64219815Spjd return (vsnprintf(str + len, size - len, fmt, ap)); 65219815Spjd} 66219815Spjd 67219815Spjdint 68219815Spjdsnprlcat(char *str, size_t size, const char *fmt, ...) 69219815Spjd{ 70219815Spjd va_list ap; 71219815Spjd int result; 72219815Spjd 73219815Spjd va_start(ap, fmt); 74219815Spjd result = vsnprlcat(str, size, fmt, ap); 75219815Spjd va_end(ap); 76219815Spjd return (result); 77219815Spjd} 78219815Spjd 79219815Spjdint 80204076Spjdprovinfo(struct hast_resource *res, bool dowrite) 81204076Spjd{ 82204076Spjd struct stat sb; 83204076Spjd 84218138Spjd PJDLOG_ASSERT(res->hr_localpath != NULL && 85218138Spjd res->hr_localpath[0] != '\0'); 86204076Spjd 87204076Spjd if (res->hr_localfd == -1) { 88204076Spjd res->hr_localfd = open(res->hr_localpath, 89204076Spjd dowrite ? O_RDWR : O_RDONLY); 90229945Spjd if (res->hr_localfd == -1) { 91225781Spjd pjdlog_errno(LOG_ERR, "Unable to open %s", 92225781Spjd res->hr_localpath); 93204076Spjd return (-1); 94204076Spjd } 95204076Spjd } 96229945Spjd if (fstat(res->hr_localfd, &sb) == -1) { 97225781Spjd pjdlog_errno(LOG_ERR, "Unable to stat %s", res->hr_localpath); 98204076Spjd return (-1); 99204076Spjd } 100204076Spjd if (S_ISCHR(sb.st_mode)) { 101204076Spjd /* 102204076Spjd * If this is character device, it is most likely GEOM provider. 103204076Spjd */ 104204076Spjd if (ioctl(res->hr_localfd, DIOCGMEDIASIZE, 105229945Spjd &res->hr_local_mediasize) == -1) { 106225781Spjd pjdlog_errno(LOG_ERR, 107204076Spjd "Unable obtain provider %s mediasize", 108225781Spjd res->hr_localpath); 109204076Spjd return (-1); 110204076Spjd } 111204076Spjd if (ioctl(res->hr_localfd, DIOCGSECTORSIZE, 112229945Spjd &res->hr_local_sectorsize) == -1) { 113225781Spjd pjdlog_errno(LOG_ERR, 114204076Spjd "Unable obtain provider %s sectorsize", 115225781Spjd res->hr_localpath); 116204076Spjd return (-1); 117204076Spjd } 118204076Spjd } else if (S_ISREG(sb.st_mode)) { 119204076Spjd /* 120204076Spjd * We also support regular files for which we hardcode 121204076Spjd * sector size of 512 bytes. 122204076Spjd */ 123204076Spjd res->hr_local_mediasize = sb.st_size; 124204076Spjd res->hr_local_sectorsize = 512; 125204076Spjd } else { 126204076Spjd /* 127204076Spjd * We support no other file types. 128204076Spjd */ 129204076Spjd pjdlog_error("%s is neither GEOM provider nor regular file.", 130204076Spjd res->hr_localpath); 131204076Spjd errno = EFTYPE; 132204076Spjd return (-1); 133204076Spjd } 134204076Spjd return (0); 135204076Spjd} 136204076Spjd 137204076Spjdconst char * 138204076Spjdrole2str(int role) 139204076Spjd{ 140204076Spjd 141219864Spjd switch (role) { 142219864Spjd case HAST_ROLE_INIT: 143204076Spjd return ("init"); 144219864Spjd case HAST_ROLE_PRIMARY: 145204076Spjd return ("primary"); 146219864Spjd case HAST_ROLE_SECONDARY: 147204076Spjd return ("secondary"); 148204076Spjd } 149204076Spjd return ("unknown"); 150204076Spjd} 151218048Spjd 152218048Spjdint 153229699Spjddrop_privs(const struct hast_resource *res) 154218048Spjd{ 155221899Spjd char jailhost[sizeof(res->hr_name) * 2]; 156221899Spjd struct jail jailst; 157218048Spjd struct passwd *pw; 158218048Spjd uid_t ruid, euid, suid; 159218048Spjd gid_t rgid, egid, sgid; 160218048Spjd gid_t gidset[1]; 161221899Spjd bool capsicum, jailed; 162218048Spjd 163218048Spjd /* 164218048Spjd * According to getpwnam(3) we have to clear errno before calling the 165218048Spjd * function to be able to distinguish between an error and missing 166218048Spjd * entry (with is not treated as error by getpwnam(3)). 167218048Spjd */ 168218048Spjd errno = 0; 169218048Spjd pw = getpwnam(HAST_USER); 170218048Spjd if (pw == NULL) { 171218048Spjd if (errno != 0) { 172225781Spjd pjdlog_errno(LOG_ERR, 173225781Spjd "Unable to find info about '%s' user", HAST_USER); 174218048Spjd return (-1); 175218048Spjd } else { 176218048Spjd pjdlog_error("'%s' user doesn't exist.", HAST_USER); 177218048Spjd errno = ENOENT; 178218048Spjd return (-1); 179218048Spjd } 180218048Spjd } 181221899Spjd 182221899Spjd bzero(&jailst, sizeof(jailst)); 183221899Spjd jailst.version = JAIL_API_VERSION; 184221899Spjd jailst.path = pw->pw_dir; 185221899Spjd if (res == NULL) { 186221899Spjd (void)snprintf(jailhost, sizeof(jailhost), "hastctl"); 187221899Spjd } else { 188221899Spjd (void)snprintf(jailhost, sizeof(jailhost), "hastd: %s (%s)", 189221899Spjd res->hr_name, role2str(res->hr_role)); 190218048Spjd } 191221899Spjd jailst.hostname = jailhost; 192221899Spjd jailst.jailname = NULL; 193221899Spjd jailst.ip4s = 0; 194221899Spjd jailst.ip4 = NULL; 195221899Spjd jailst.ip6s = 0; 196221899Spjd jailst.ip6 = NULL; 197221899Spjd if (jail(&jailst) >= 0) { 198221899Spjd jailed = true; 199221899Spjd } else { 200221899Spjd jailed = false; 201221899Spjd pjdlog_errno(LOG_WARNING, 202221899Spjd "Unable to jail to directory to %s", pw->pw_dir); 203221899Spjd if (chroot(pw->pw_dir) == -1) { 204225781Spjd pjdlog_errno(LOG_ERR, 205221899Spjd "Unable to change root directory to %s", 206225781Spjd pw->pw_dir); 207221899Spjd return (-1); 208221899Spjd } 209221899Spjd } 210218048Spjd PJDLOG_VERIFY(chdir("/") == 0); 211218048Spjd gidset[0] = pw->pw_gid; 212218048Spjd if (setgroups(1, gidset) == -1) { 213225781Spjd pjdlog_errno(LOG_ERR, "Unable to set groups to gid %u", 214225781Spjd (unsigned int)pw->pw_gid); 215218048Spjd return (-1); 216218048Spjd } 217218048Spjd if (setgid(pw->pw_gid) == -1) { 218225781Spjd pjdlog_errno(LOG_ERR, "Unable to set gid to %u", 219225781Spjd (unsigned int)pw->pw_gid); 220218048Spjd return (-1); 221218048Spjd } 222218048Spjd if (setuid(pw->pw_uid) == -1) { 223225781Spjd pjdlog_errno(LOG_ERR, "Unable to set uid to %u", 224225781Spjd (unsigned int)pw->pw_uid); 225218048Spjd return (-1); 226218048Spjd } 227218048Spjd 228223585Spjd#ifdef HAVE_CAPSICUM 229248297Spjd capsicum = (cap_enter() == 0); 230248297Spjd if (!capsicum) { 231248297Spjd pjdlog_common(LOG_DEBUG, 1, errno, 232248297Spjd "Unable to sandbox using capsicum"); 233248297Spjd } else if (res != NULL) { 234255219Spjd cap_rights_t rights; 235248297Spjd static const unsigned long geomcmds[] = { 236248297Spjd DIOCGDELETE, 237248297Spjd DIOCGFLUSH 238248297Spjd }; 239248297Spjd 240248297Spjd PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY || 241248297Spjd res->hr_role == HAST_ROLE_SECONDARY); 242248297Spjd 243255219Spjd cap_rights_init(&rights, CAP_FLOCK, CAP_IOCTL, CAP_PREAD, 244255219Spjd CAP_PWRITE); 245255219Spjd if (cap_rights_limit(res->hr_localfd, &rights) == -1) { 246248297Spjd pjdlog_errno(LOG_ERR, 247248297Spjd "Unable to limit capability rights on local descriptor"); 248223584Spjd } 249248297Spjd if (cap_ioctls_limit(res->hr_localfd, geomcmds, 250298317Saraujo nitems(geomcmds)) == -1) { 251248297Spjd pjdlog_errno(LOG_ERR, 252248297Spjd "Unable to limit allowed GEOM ioctls"); 253248297Spjd } 254248297Spjd 255248297Spjd if (res->hr_role == HAST_ROLE_PRIMARY) { 256248297Spjd static const unsigned long ggatecmds[] = { 257248297Spjd G_GATE_CMD_MODIFY, 258248297Spjd G_GATE_CMD_START, 259248297Spjd G_GATE_CMD_DONE, 260248297Spjd G_GATE_CMD_DESTROY 261248297Spjd }; 262248297Spjd 263255219Spjd cap_rights_init(&rights, CAP_IOCTL); 264255219Spjd if (cap_rights_limit(res->hr_ggatefd, &rights) == -1) { 265248297Spjd pjdlog_errno(LOG_ERR, 266248297Spjd "Unable to limit capability rights to CAP_IOCTL on ggate descriptor"); 267248297Spjd } 268248297Spjd if (cap_ioctls_limit(res->hr_ggatefd, ggatecmds, 269298317Saraujo nitems(ggatecmds)) == -1) { 270248297Spjd pjdlog_errno(LOG_ERR, 271248297Spjd "Unable to limit allowed ggate ioctls"); 272248297Spjd } 273248297Spjd } 274248297Spjd } 275248297Spjd#else 276248297Spjd capsicum = false; 277223585Spjd#endif 278221898Spjd 279218048Spjd /* 280218048Spjd * Better be sure that everything succeeded. 281218048Spjd */ 282218048Spjd PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0); 283218048Spjd PJDLOG_VERIFY(ruid == pw->pw_uid); 284218048Spjd PJDLOG_VERIFY(euid == pw->pw_uid); 285218048Spjd PJDLOG_VERIFY(suid == pw->pw_uid); 286218048Spjd PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0); 287218048Spjd PJDLOG_VERIFY(rgid == pw->pw_gid); 288218048Spjd PJDLOG_VERIFY(egid == pw->pw_gid); 289218048Spjd PJDLOG_VERIFY(sgid == pw->pw_gid); 290218048Spjd PJDLOG_VERIFY(getgroups(0, NULL) == 1); 291218048Spjd PJDLOG_VERIFY(getgroups(1, gidset) == 1); 292218048Spjd PJDLOG_VERIFY(gidset[0] == pw->pw_gid); 293218048Spjd 294219847Spjd pjdlog_debug(1, 295221899Spjd "Privileges successfully dropped using %s%s+setgid+setuid.", 296221899Spjd capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot"); 297219847Spjd 298218048Spjd return (0); 299218048Spjd} 300