xref: /freebsd/sbin/hastd/subr.c (revision c2bce4a2fcf3083607e00a1734b47c249751c8a8)
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/types.h>
36 #include <sys/disk.h>
37 #include <sys/ioctl.h>
38 #include <sys/stat.h>
39 
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <pwd.h>
43 #include <stdarg.h>
44 #include <stdbool.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <unistd.h>
48 
49 #include <pjdlog.h>
50 
51 #include "hast.h"
52 #include "subr.h"
53 
54 int
55 vsnprlcat(char *str, size_t size, const char *fmt, va_list ap)
56 {
57 	size_t len;
58 
59 	len = strlen(str);
60 	return (vsnprintf(str + len, size - len, fmt, ap));
61 }
62 
63 int
64 snprlcat(char *str, size_t size, const char *fmt, ...)
65 {
66 	va_list ap;
67 	int result;
68 
69 	va_start(ap, fmt);
70 	result = vsnprlcat(str, size, fmt, ap);
71 	va_end(ap);
72 	return (result);
73 }
74 
75 int
76 provinfo(struct hast_resource *res, bool dowrite)
77 {
78 	struct stat sb;
79 
80 	PJDLOG_ASSERT(res->hr_localpath != NULL &&
81 	    res->hr_localpath[0] != '\0');
82 
83 	if (res->hr_localfd == -1) {
84 		res->hr_localfd = open(res->hr_localpath,
85 		    dowrite ? O_RDWR : O_RDONLY);
86 		if (res->hr_localfd < 0) {
87 			KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to open %s",
88 			    res->hr_localpath));
89 			return (-1);
90 		}
91 	}
92 	if (fstat(res->hr_localfd, &sb) < 0) {
93 		KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to stat %s",
94 		    res->hr_localpath));
95 		return (-1);
96 	}
97 	if (S_ISCHR(sb.st_mode)) {
98 		/*
99 		 * If this is character device, it is most likely GEOM provider.
100 		 */
101 		if (ioctl(res->hr_localfd, DIOCGMEDIASIZE,
102 		    &res->hr_local_mediasize) < 0) {
103 			KEEP_ERRNO(pjdlog_errno(LOG_ERR,
104 			    "Unable obtain provider %s mediasize",
105 			    res->hr_localpath));
106 			return (-1);
107 		}
108 		if (ioctl(res->hr_localfd, DIOCGSECTORSIZE,
109 		    &res->hr_local_sectorsize) < 0) {
110 			KEEP_ERRNO(pjdlog_errno(LOG_ERR,
111 			    "Unable obtain provider %s sectorsize",
112 			    res->hr_localpath));
113 			return (-1);
114 		}
115 	} else if (S_ISREG(sb.st_mode)) {
116 		/*
117 		 * We also support regular files for which we hardcode
118 		 * sector size of 512 bytes.
119 		 */
120 		res->hr_local_mediasize = sb.st_size;
121 		res->hr_local_sectorsize = 512;
122 	} else {
123 		/*
124 		 * We support no other file types.
125 		 */
126 		pjdlog_error("%s is neither GEOM provider nor regular file.",
127 		    res->hr_localpath);
128 		errno = EFTYPE;
129 		return (-1);
130 	}
131 	return (0);
132 }
133 
134 const char *
135 role2str(int role)
136 {
137 
138 	switch (role) {
139 	case HAST_ROLE_INIT:
140 		return ("init");
141 	case HAST_ROLE_PRIMARY:
142 		return ("primary");
143 	case HAST_ROLE_SECONDARY:
144 		return ("secondary");
145 	}
146 	return ("unknown");
147 }
148 
149 int
150 drop_privs(bool usecapsicum)
151 {
152 	struct passwd *pw;
153 	uid_t ruid, euid, suid;
154 	gid_t rgid, egid, sgid;
155 	gid_t gidset[1];
156 
157 	if (usecapsicum) {
158 		if (cap_enter() == 0) {
159 			pjdlog_debug(1,
160 			    "Privileges successfully dropped using capsicum.");
161 			return (0);
162 		}
163 		pjdlog_errno(LOG_WARNING, "Unable to sandbox using capsicum");
164 	}
165 
166 	/*
167 	 * According to getpwnam(3) we have to clear errno before calling the
168 	 * function to be able to distinguish between an error and missing
169 	 * entry (with is not treated as error by getpwnam(3)).
170 	 */
171 	errno = 0;
172 	pw = getpwnam(HAST_USER);
173 	if (pw == NULL) {
174 		if (errno != 0) {
175 			KEEP_ERRNO(pjdlog_errno(LOG_ERR,
176 			    "Unable to find info about '%s' user", HAST_USER));
177 			return (-1);
178 		} else {
179 			pjdlog_error("'%s' user doesn't exist.", HAST_USER);
180 			errno = ENOENT;
181 			return (-1);
182 		}
183 	}
184 	if (chroot(pw->pw_dir) == -1) {
185 		KEEP_ERRNO(pjdlog_errno(LOG_ERR,
186 		    "Unable to change root directory to %s", pw->pw_dir));
187 		return (-1);
188 	}
189 	PJDLOG_VERIFY(chdir("/") == 0);
190 	gidset[0] = pw->pw_gid;
191 	if (setgroups(1, gidset) == -1) {
192 		KEEP_ERRNO(pjdlog_errno(LOG_ERR,
193 		    "Unable to set groups to gid %u",
194 		    (unsigned int)pw->pw_gid));
195 		return (-1);
196 	}
197 	if (setgid(pw->pw_gid) == -1) {
198 		KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
199 		    (unsigned int)pw->pw_gid));
200 		return (-1);
201 	}
202 	if (setuid(pw->pw_uid) == -1) {
203 		KEEP_ERRNO(pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
204 		    (unsigned int)pw->pw_uid));
205 		return (-1);
206 	}
207 
208 	/*
209 	 * Better be sure that everything succeeded.
210 	 */
211 	PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
212 	PJDLOG_VERIFY(ruid == pw->pw_uid);
213 	PJDLOG_VERIFY(euid == pw->pw_uid);
214 	PJDLOG_VERIFY(suid == pw->pw_uid);
215 	PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
216 	PJDLOG_VERIFY(rgid == pw->pw_gid);
217 	PJDLOG_VERIFY(egid == pw->pw_gid);
218 	PJDLOG_VERIFY(sgid == pw->pw_gid);
219 	PJDLOG_VERIFY(getgroups(0, NULL) == 1);
220 	PJDLOG_VERIFY(getgroups(1, gidset) == 1);
221 	PJDLOG_VERIFY(gidset[0] == pw->pw_gid);
222 
223 	pjdlog_debug(1,
224 	    "Privileges successfully dropped using chroot+setgid+setuid.");
225 
226 	return (0);
227 }
228