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