132115b10SPawel Jakub Dawidek /*-
2*4d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
31de7b4b8SPedro F. Giffuni *
432115b10SPawel Jakub Dawidek * Copyright (c) 2010 The FreeBSD Foundation
56d51b7d5SPawel Jakub Dawidek * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
632115b10SPawel Jakub Dawidek * All rights reserved.
732115b10SPawel Jakub Dawidek *
832115b10SPawel Jakub Dawidek * This software was developed by Pawel Jakub Dawidek under sponsorship from
932115b10SPawel Jakub Dawidek * the FreeBSD Foundation.
1032115b10SPawel Jakub Dawidek *
1132115b10SPawel Jakub Dawidek * Redistribution and use in source and binary forms, with or without
1232115b10SPawel Jakub Dawidek * modification, are permitted provided that the following conditions
1332115b10SPawel Jakub Dawidek * are met:
1432115b10SPawel Jakub Dawidek * 1. Redistributions of source code must retain the above copyright
1532115b10SPawel Jakub Dawidek * notice, this list of conditions and the following disclaimer.
1632115b10SPawel Jakub Dawidek * 2. Redistributions in binary form must reproduce the above copyright
1732115b10SPawel Jakub Dawidek * notice, this list of conditions and the following disclaimer in the
1832115b10SPawel Jakub Dawidek * documentation and/or other materials provided with the distribution.
1932115b10SPawel Jakub Dawidek *
2032115b10SPawel Jakub Dawidek * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
2132115b10SPawel Jakub Dawidek * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2232115b10SPawel Jakub Dawidek * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2332115b10SPawel Jakub Dawidek * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
2432115b10SPawel Jakub Dawidek * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2532115b10SPawel Jakub Dawidek * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2632115b10SPawel Jakub Dawidek * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2732115b10SPawel Jakub Dawidek * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2832115b10SPawel Jakub Dawidek * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2932115b10SPawel Jakub Dawidek * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3032115b10SPawel Jakub Dawidek * SUCH DAMAGE.
3132115b10SPawel Jakub Dawidek */
3232115b10SPawel Jakub Dawidek
330cddb12fSPawel Jakub Dawidek #include <sys/param.h>
3432115b10SPawel Jakub Dawidek #include <sys/disk.h>
3532115b10SPawel Jakub Dawidek #include <sys/ioctl.h>
360cddb12fSPawel Jakub Dawidek #include <sys/jail.h>
3732115b10SPawel Jakub Dawidek #include <sys/stat.h>
3851ea07d7SPawel Jakub Dawidek #ifdef HAVE_CAPSICUM
39b881b8beSRobert Watson #include <sys/capsicum.h>
4051ea07d7SPawel Jakub Dawidek #include <geom/gate/g_gate.h>
4151ea07d7SPawel Jakub Dawidek #endif
4232115b10SPawel Jakub Dawidek
4332115b10SPawel Jakub Dawidek #include <errno.h>
4432115b10SPawel Jakub Dawidek #include <fcntl.h>
4549499e98SPawel Jakub Dawidek #include <pwd.h>
469925a680SPawel Jakub Dawidek #include <stdarg.h>
474d8dc3b8SPawel Jakub Dawidek #include <stdbool.h>
489925a680SPawel Jakub Dawidek #include <stdio.h>
499925a680SPawel Jakub Dawidek #include <string.h>
5049499e98SPawel Jakub Dawidek #include <unistd.h>
5132115b10SPawel Jakub Dawidek
5232115b10SPawel Jakub Dawidek #include <pjdlog.h>
5332115b10SPawel Jakub Dawidek
5432115b10SPawel Jakub Dawidek #include "hast.h"
5532115b10SPawel Jakub Dawidek #include "subr.h"
5632115b10SPawel Jakub Dawidek
5732115b10SPawel Jakub Dawidek int
vsnprlcat(char * str,size_t size,const char * fmt,va_list ap)589925a680SPawel Jakub Dawidek vsnprlcat(char *str, size_t size, const char *fmt, va_list ap)
599925a680SPawel Jakub Dawidek {
609925a680SPawel Jakub Dawidek size_t len;
619925a680SPawel Jakub Dawidek
629925a680SPawel Jakub Dawidek len = strlen(str);
639925a680SPawel Jakub Dawidek return (vsnprintf(str + len, size - len, fmt, ap));
649925a680SPawel Jakub Dawidek }
659925a680SPawel Jakub Dawidek
669925a680SPawel Jakub Dawidek int
snprlcat(char * str,size_t size,const char * fmt,...)679925a680SPawel Jakub Dawidek snprlcat(char *str, size_t size, const char *fmt, ...)
689925a680SPawel Jakub Dawidek {
699925a680SPawel Jakub Dawidek va_list ap;
709925a680SPawel Jakub Dawidek int result;
719925a680SPawel Jakub Dawidek
729925a680SPawel Jakub Dawidek va_start(ap, fmt);
739925a680SPawel Jakub Dawidek result = vsnprlcat(str, size, fmt, ap);
749925a680SPawel Jakub Dawidek va_end(ap);
759925a680SPawel Jakub Dawidek return (result);
769925a680SPawel Jakub Dawidek }
779925a680SPawel Jakub Dawidek
789925a680SPawel Jakub Dawidek int
provinfo(struct hast_resource * res,bool dowrite)7932115b10SPawel Jakub Dawidek provinfo(struct hast_resource *res, bool dowrite)
8032115b10SPawel Jakub Dawidek {
8132115b10SPawel Jakub Dawidek struct stat sb;
8232115b10SPawel Jakub Dawidek
832ec483c5SPawel Jakub Dawidek PJDLOG_ASSERT(res->hr_localpath != NULL &&
842ec483c5SPawel Jakub Dawidek res->hr_localpath[0] != '\0');
8532115b10SPawel Jakub Dawidek
8632115b10SPawel Jakub Dawidek if (res->hr_localfd == -1) {
8732115b10SPawel Jakub Dawidek res->hr_localfd = open(res->hr_localpath,
8832115b10SPawel Jakub Dawidek dowrite ? O_RDWR : O_RDONLY);
892b1b224dSPawel Jakub Dawidek if (res->hr_localfd == -1) {
901ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR, "Unable to open %s",
911ebc0407SPawel Jakub Dawidek res->hr_localpath);
9232115b10SPawel Jakub Dawidek return (-1);
9332115b10SPawel Jakub Dawidek }
9432115b10SPawel Jakub Dawidek }
952b1b224dSPawel Jakub Dawidek if (fstat(res->hr_localfd, &sb) == -1) {
961ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR, "Unable to stat %s", res->hr_localpath);
9732115b10SPawel Jakub Dawidek return (-1);
9832115b10SPawel Jakub Dawidek }
9932115b10SPawel Jakub Dawidek if (S_ISCHR(sb.st_mode)) {
10032115b10SPawel Jakub Dawidek /*
10132115b10SPawel Jakub Dawidek * If this is character device, it is most likely GEOM provider.
10232115b10SPawel Jakub Dawidek */
10332115b10SPawel Jakub Dawidek if (ioctl(res->hr_localfd, DIOCGMEDIASIZE,
1042b1b224dSPawel Jakub Dawidek &res->hr_local_mediasize) == -1) {
1051ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
10632115b10SPawel Jakub Dawidek "Unable obtain provider %s mediasize",
1071ebc0407SPawel Jakub Dawidek res->hr_localpath);
10832115b10SPawel Jakub Dawidek return (-1);
10932115b10SPawel Jakub Dawidek }
11032115b10SPawel Jakub Dawidek if (ioctl(res->hr_localfd, DIOCGSECTORSIZE,
1112b1b224dSPawel Jakub Dawidek &res->hr_local_sectorsize) == -1) {
1121ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
11332115b10SPawel Jakub Dawidek "Unable obtain provider %s sectorsize",
1141ebc0407SPawel Jakub Dawidek res->hr_localpath);
11532115b10SPawel Jakub Dawidek return (-1);
11632115b10SPawel Jakub Dawidek }
11732115b10SPawel Jakub Dawidek } else if (S_ISREG(sb.st_mode)) {
11832115b10SPawel Jakub Dawidek /*
11932115b10SPawel Jakub Dawidek * We also support regular files for which we hardcode
12032115b10SPawel Jakub Dawidek * sector size of 512 bytes.
12132115b10SPawel Jakub Dawidek */
12232115b10SPawel Jakub Dawidek res->hr_local_mediasize = sb.st_size;
12332115b10SPawel Jakub Dawidek res->hr_local_sectorsize = 512;
12432115b10SPawel Jakub Dawidek } else {
12532115b10SPawel Jakub Dawidek /*
12632115b10SPawel Jakub Dawidek * We support no other file types.
12732115b10SPawel Jakub Dawidek */
12832115b10SPawel Jakub Dawidek pjdlog_error("%s is neither GEOM provider nor regular file.",
12932115b10SPawel Jakub Dawidek res->hr_localpath);
13032115b10SPawel Jakub Dawidek errno = EFTYPE;
13132115b10SPawel Jakub Dawidek return (-1);
13232115b10SPawel Jakub Dawidek }
13332115b10SPawel Jakub Dawidek return (0);
13432115b10SPawel Jakub Dawidek }
13532115b10SPawel Jakub Dawidek
13632115b10SPawel Jakub Dawidek const char *
role2str(int role)13732115b10SPawel Jakub Dawidek role2str(int role)
13832115b10SPawel Jakub Dawidek {
13932115b10SPawel Jakub Dawidek
14032115b10SPawel Jakub Dawidek switch (role) {
14132115b10SPawel Jakub Dawidek case HAST_ROLE_INIT:
14232115b10SPawel Jakub Dawidek return ("init");
14332115b10SPawel Jakub Dawidek case HAST_ROLE_PRIMARY:
14432115b10SPawel Jakub Dawidek return ("primary");
14532115b10SPawel Jakub Dawidek case HAST_ROLE_SECONDARY:
14632115b10SPawel Jakub Dawidek return ("secondary");
14732115b10SPawel Jakub Dawidek }
14832115b10SPawel Jakub Dawidek return ("unknown");
14932115b10SPawel Jakub Dawidek }
15049499e98SPawel Jakub Dawidek
15149499e98SPawel Jakub Dawidek int
drop_privs(const struct hast_resource * res)152f78fe260SPawel Jakub Dawidek drop_privs(const struct hast_resource *res)
15349499e98SPawel Jakub Dawidek {
1540cddb12fSPawel Jakub Dawidek char jailhost[sizeof(res->hr_name) * 2];
1550cddb12fSPawel Jakub Dawidek struct jail jailst;
15649499e98SPawel Jakub Dawidek struct passwd *pw;
15749499e98SPawel Jakub Dawidek uid_t ruid, euid, suid;
15849499e98SPawel Jakub Dawidek gid_t rgid, egid, sgid;
15949499e98SPawel Jakub Dawidek gid_t gidset[1];
1600cddb12fSPawel Jakub Dawidek bool capsicum, jailed;
1614d8dc3b8SPawel Jakub Dawidek
16249499e98SPawel Jakub Dawidek /*
16349499e98SPawel Jakub Dawidek * According to getpwnam(3) we have to clear errno before calling the
16449499e98SPawel Jakub Dawidek * function to be able to distinguish between an error and missing
16549499e98SPawel Jakub Dawidek * entry (with is not treated as error by getpwnam(3)).
16649499e98SPawel Jakub Dawidek */
16749499e98SPawel Jakub Dawidek errno = 0;
16849499e98SPawel Jakub Dawidek pw = getpwnam(HAST_USER);
16949499e98SPawel Jakub Dawidek if (pw == NULL) {
17049499e98SPawel Jakub Dawidek if (errno != 0) {
1711ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
1721ebc0407SPawel Jakub Dawidek "Unable to find info about '%s' user", HAST_USER);
17349499e98SPawel Jakub Dawidek return (-1);
17449499e98SPawel Jakub Dawidek } else {
17549499e98SPawel Jakub Dawidek pjdlog_error("'%s' user doesn't exist.", HAST_USER);
17649499e98SPawel Jakub Dawidek errno = ENOENT;
17749499e98SPawel Jakub Dawidek return (-1);
17849499e98SPawel Jakub Dawidek }
17949499e98SPawel Jakub Dawidek }
1800cddb12fSPawel Jakub Dawidek
1810cddb12fSPawel Jakub Dawidek bzero(&jailst, sizeof(jailst));
1820cddb12fSPawel Jakub Dawidek jailst.version = JAIL_API_VERSION;
1830cddb12fSPawel Jakub Dawidek jailst.path = pw->pw_dir;
1840cddb12fSPawel Jakub Dawidek if (res == NULL) {
1850cddb12fSPawel Jakub Dawidek (void)snprintf(jailhost, sizeof(jailhost), "hastctl");
1860cddb12fSPawel Jakub Dawidek } else {
1870cddb12fSPawel Jakub Dawidek (void)snprintf(jailhost, sizeof(jailhost), "hastd: %s (%s)",
1880cddb12fSPawel Jakub Dawidek res->hr_name, role2str(res->hr_role));
1890cddb12fSPawel Jakub Dawidek }
1900cddb12fSPawel Jakub Dawidek jailst.hostname = jailhost;
1910cddb12fSPawel Jakub Dawidek jailst.jailname = NULL;
1920cddb12fSPawel Jakub Dawidek jailst.ip4s = 0;
1930cddb12fSPawel Jakub Dawidek jailst.ip4 = NULL;
1940cddb12fSPawel Jakub Dawidek jailst.ip6s = 0;
1950cddb12fSPawel Jakub Dawidek jailst.ip6 = NULL;
1960cddb12fSPawel Jakub Dawidek if (jail(&jailst) >= 0) {
1970cddb12fSPawel Jakub Dawidek jailed = true;
1980cddb12fSPawel Jakub Dawidek } else {
1990cddb12fSPawel Jakub Dawidek jailed = false;
2000cddb12fSPawel Jakub Dawidek pjdlog_errno(LOG_WARNING,
2010cddb12fSPawel Jakub Dawidek "Unable to jail to directory to %s", pw->pw_dir);
20249499e98SPawel Jakub Dawidek if (chroot(pw->pw_dir) == -1) {
2031ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
2040cddb12fSPawel Jakub Dawidek "Unable to change root directory to %s",
2051ebc0407SPawel Jakub Dawidek pw->pw_dir);
20649499e98SPawel Jakub Dawidek return (-1);
20749499e98SPawel Jakub Dawidek }
2080cddb12fSPawel Jakub Dawidek }
20949499e98SPawel Jakub Dawidek PJDLOG_VERIFY(chdir("/") == 0);
21049499e98SPawel Jakub Dawidek gidset[0] = pw->pw_gid;
21149499e98SPawel Jakub Dawidek if (setgroups(1, gidset) == -1) {
2121ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR, "Unable to set groups to gid %u",
2131ebc0407SPawel Jakub Dawidek (unsigned int)pw->pw_gid);
21449499e98SPawel Jakub Dawidek return (-1);
21549499e98SPawel Jakub Dawidek }
21649499e98SPawel Jakub Dawidek if (setgid(pw->pw_gid) == -1) {
2171ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
2181ebc0407SPawel Jakub Dawidek (unsigned int)pw->pw_gid);
21949499e98SPawel Jakub Dawidek return (-1);
22049499e98SPawel Jakub Dawidek }
22149499e98SPawel Jakub Dawidek if (setuid(pw->pw_uid) == -1) {
2221ebc0407SPawel Jakub Dawidek pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
2231ebc0407SPawel Jakub Dawidek (unsigned int)pw->pw_uid);
22449499e98SPawel Jakub Dawidek return (-1);
22549499e98SPawel Jakub Dawidek }
22649499e98SPawel Jakub Dawidek
227699b26bdSPawel Jakub Dawidek #ifdef HAVE_CAPSICUM
2280cddb12fSPawel Jakub Dawidek capsicum = (cap_enter() == 0);
229133d75edSPawel Jakub Dawidek if (!capsicum) {
230133d75edSPawel Jakub Dawidek pjdlog_common(LOG_DEBUG, 1, errno,
231133d75edSPawel Jakub Dawidek "Unable to sandbox using capsicum");
23251ea07d7SPawel Jakub Dawidek } else if (res != NULL) {
2337008be5bSPawel Jakub Dawidek cap_rights_t rights;
23451ea07d7SPawel Jakub Dawidek static const unsigned long geomcmds[] = {
23551ea07d7SPawel Jakub Dawidek DIOCGDELETE,
23651ea07d7SPawel Jakub Dawidek DIOCGFLUSH
23751ea07d7SPawel Jakub Dawidek };
23851ea07d7SPawel Jakub Dawidek
23951ea07d7SPawel Jakub Dawidek PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY ||
24051ea07d7SPawel Jakub Dawidek res->hr_role == HAST_ROLE_SECONDARY);
24151ea07d7SPawel Jakub Dawidek
2427008be5bSPawel Jakub Dawidek cap_rights_init(&rights, CAP_FLOCK, CAP_IOCTL, CAP_PREAD,
2437008be5bSPawel Jakub Dawidek CAP_PWRITE);
2447008be5bSPawel Jakub Dawidek if (cap_rights_limit(res->hr_localfd, &rights) == -1) {
24551ea07d7SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
24651ea07d7SPawel Jakub Dawidek "Unable to limit capability rights on local descriptor");
247133d75edSPawel Jakub Dawidek }
24851ea07d7SPawel Jakub Dawidek if (cap_ioctls_limit(res->hr_localfd, geomcmds,
24946df5db8SMarcelo Araujo nitems(geomcmds)) == -1) {
25051ea07d7SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
25151ea07d7SPawel Jakub Dawidek "Unable to limit allowed GEOM ioctls");
25251ea07d7SPawel Jakub Dawidek }
25351ea07d7SPawel Jakub Dawidek
25451ea07d7SPawel Jakub Dawidek if (res->hr_role == HAST_ROLE_PRIMARY) {
25551ea07d7SPawel Jakub Dawidek static const unsigned long ggatecmds[] = {
25651ea07d7SPawel Jakub Dawidek G_GATE_CMD_MODIFY,
25751ea07d7SPawel Jakub Dawidek G_GATE_CMD_START,
25851ea07d7SPawel Jakub Dawidek G_GATE_CMD_DONE,
25951ea07d7SPawel Jakub Dawidek G_GATE_CMD_DESTROY
26051ea07d7SPawel Jakub Dawidek };
26151ea07d7SPawel Jakub Dawidek
2627008be5bSPawel Jakub Dawidek cap_rights_init(&rights, CAP_IOCTL);
2637008be5bSPawel Jakub Dawidek if (cap_rights_limit(res->hr_ggatefd, &rights) == -1) {
26451ea07d7SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
26551ea07d7SPawel Jakub Dawidek "Unable to limit capability rights to CAP_IOCTL on ggate descriptor");
26651ea07d7SPawel Jakub Dawidek }
26751ea07d7SPawel Jakub Dawidek if (cap_ioctls_limit(res->hr_ggatefd, ggatecmds,
26846df5db8SMarcelo Araujo nitems(ggatecmds)) == -1) {
26951ea07d7SPawel Jakub Dawidek pjdlog_errno(LOG_ERR,
27051ea07d7SPawel Jakub Dawidek "Unable to limit allowed ggate ioctls");
27151ea07d7SPawel Jakub Dawidek }
27251ea07d7SPawel Jakub Dawidek }
27351ea07d7SPawel Jakub Dawidek }
27451ea07d7SPawel Jakub Dawidek #else
275bcc9f321SPawel Jakub Dawidek capsicum = false;
27651ea07d7SPawel Jakub Dawidek #endif
277bcc9f321SPawel Jakub Dawidek
27849499e98SPawel Jakub Dawidek /*
27949499e98SPawel Jakub Dawidek * Better be sure that everything succeeded.
28049499e98SPawel Jakub Dawidek */
28149499e98SPawel Jakub Dawidek PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
28249499e98SPawel Jakub Dawidek PJDLOG_VERIFY(ruid == pw->pw_uid);
28349499e98SPawel Jakub Dawidek PJDLOG_VERIFY(euid == pw->pw_uid);
28449499e98SPawel Jakub Dawidek PJDLOG_VERIFY(suid == pw->pw_uid);
28549499e98SPawel Jakub Dawidek PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
28649499e98SPawel Jakub Dawidek PJDLOG_VERIFY(rgid == pw->pw_gid);
28749499e98SPawel Jakub Dawidek PJDLOG_VERIFY(egid == pw->pw_gid);
28849499e98SPawel Jakub Dawidek PJDLOG_VERIFY(sgid == pw->pw_gid);
28949499e98SPawel Jakub Dawidek PJDLOG_VERIFY(getgroups(0, NULL) == 1);
29049499e98SPawel Jakub Dawidek PJDLOG_VERIFY(getgroups(1, gidset) == 1);
29149499e98SPawel Jakub Dawidek PJDLOG_VERIFY(gidset[0] == pw->pw_gid);
29249499e98SPawel Jakub Dawidek
2934d8dc3b8SPawel Jakub Dawidek pjdlog_debug(1,
2940cddb12fSPawel Jakub Dawidek "Privileges successfully dropped using %s%s+setgid+setuid.",
2950cddb12fSPawel Jakub Dawidek capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot");
2964d8dc3b8SPawel Jakub Dawidek
29749499e98SPawel Jakub Dawidek return (0);
29849499e98SPawel Jakub Dawidek }
299