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