]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sbin/hastd/subr.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / sbin / hastd / subr.c
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/param.h>
35 #include <sys/disk.h>
36 #include <sys/ioctl.h>
37 #include <sys/jail.h>
38 #include <sys/stat.h>
39 #ifdef HAVE_CAPSICUM
40 #include <sys/capability.h>
41 #include <geom/gate/g_gate.h>
42 #endif
43
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <pwd.h>
47 #include <stdarg.h>
48 #include <stdbool.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <unistd.h>
52
53 #include <pjdlog.h>
54
55 #include "hast.h"
56 #include "subr.h"
57
58 int
59 vsnprlcat(char *str, size_t size, const char *fmt, va_list ap)
60 {
61         size_t len;
62
63         len = strlen(str);
64         return (vsnprintf(str + len, size - len, fmt, ap));
65 }
66
67 int
68 snprlcat(char *str, size_t size, const char *fmt, ...)
69 {
70         va_list ap;
71         int result;
72
73         va_start(ap, fmt);
74         result = vsnprlcat(str, size, fmt, ap);
75         va_end(ap);
76         return (result);
77 }
78
79 int
80 provinfo(struct hast_resource *res, bool dowrite)
81 {
82         struct stat sb;
83
84         PJDLOG_ASSERT(res->hr_localpath != NULL &&
85             res->hr_localpath[0] != '\0');
86
87         if (res->hr_localfd == -1) {
88                 res->hr_localfd = open(res->hr_localpath,
89                     dowrite ? O_RDWR : O_RDONLY);
90                 if (res->hr_localfd == -1) {
91                         pjdlog_errno(LOG_ERR, "Unable to open %s",
92                             res->hr_localpath);
93                         return (-1);
94                 }
95         }
96         if (fstat(res->hr_localfd, &sb) == -1) {
97                 pjdlog_errno(LOG_ERR, "Unable to stat %s", res->hr_localpath);
98                 return (-1);
99         }
100         if (S_ISCHR(sb.st_mode)) {
101                 /*
102                  * If this is character device, it is most likely GEOM provider.
103                  */
104                 if (ioctl(res->hr_localfd, DIOCGMEDIASIZE,
105                     &res->hr_local_mediasize) == -1) {
106                         pjdlog_errno(LOG_ERR,
107                             "Unable obtain provider %s mediasize",
108                             res->hr_localpath);
109                         return (-1);
110                 }
111                 if (ioctl(res->hr_localfd, DIOCGSECTORSIZE,
112                     &res->hr_local_sectorsize) == -1) {
113                         pjdlog_errno(LOG_ERR,
114                             "Unable obtain provider %s sectorsize",
115                             res->hr_localpath);
116                         return (-1);
117                 }
118         } else if (S_ISREG(sb.st_mode)) {
119                 /*
120                  * We also support regular files for which we hardcode
121                  * sector size of 512 bytes.
122                  */
123                 res->hr_local_mediasize = sb.st_size;
124                 res->hr_local_sectorsize = 512;
125         } else {
126                 /*
127                  * We support no other file types.
128                  */
129                 pjdlog_error("%s is neither GEOM provider nor regular file.",
130                     res->hr_localpath);
131                 errno = EFTYPE;
132                 return (-1);
133         }
134         return (0);
135 }
136
137 const char *
138 role2str(int role)
139 {
140
141         switch (role) {
142         case HAST_ROLE_INIT:
143                 return ("init");
144         case HAST_ROLE_PRIMARY:
145                 return ("primary");
146         case HAST_ROLE_SECONDARY:
147                 return ("secondary");
148         }
149         return ("unknown");
150 }
151
152 int
153 drop_privs(const struct hast_resource *res)
154 {
155         char jailhost[sizeof(res->hr_name) * 2];
156         struct jail jailst;
157         struct passwd *pw;
158         uid_t ruid, euid, suid;
159         gid_t rgid, egid, sgid;
160         gid_t gidset[1];
161         bool capsicum, jailed;
162
163         /*
164          * According to getpwnam(3) we have to clear errno before calling the
165          * function to be able to distinguish between an error and missing
166          * entry (with is not treated as error by getpwnam(3)).
167          */
168         errno = 0;
169         pw = getpwnam(HAST_USER);
170         if (pw == NULL) {
171                 if (errno != 0) {
172                         pjdlog_errno(LOG_ERR,
173                             "Unable to find info about '%s' user", HAST_USER);
174                         return (-1);
175                 } else {
176                         pjdlog_error("'%s' user doesn't exist.", HAST_USER);
177                         errno = ENOENT;
178                         return (-1);
179                 }
180         }
181
182         bzero(&jailst, sizeof(jailst));
183         jailst.version = JAIL_API_VERSION;
184         jailst.path = pw->pw_dir;
185         if (res == NULL) {
186                 (void)snprintf(jailhost, sizeof(jailhost), "hastctl");
187         } else {
188                 (void)snprintf(jailhost, sizeof(jailhost), "hastd: %s (%s)",
189                     res->hr_name, role2str(res->hr_role));
190         }
191         jailst.hostname = jailhost;
192         jailst.jailname = NULL;
193         jailst.ip4s = 0;
194         jailst.ip4 = NULL;
195         jailst.ip6s = 0;
196         jailst.ip6 = NULL;
197         if (jail(&jailst) >= 0) {
198                 jailed = true;
199         } else {
200                 jailed = false;
201                 pjdlog_errno(LOG_WARNING,
202                     "Unable to jail to directory to %s", pw->pw_dir);
203                 if (chroot(pw->pw_dir) == -1) {
204                         pjdlog_errno(LOG_ERR,
205                             "Unable to change root directory to %s",
206                             pw->pw_dir);
207                         return (-1);
208                 }
209         }
210         PJDLOG_VERIFY(chdir("/") == 0);
211         gidset[0] = pw->pw_gid;
212         if (setgroups(1, gidset) == -1) {
213                 pjdlog_errno(LOG_ERR, "Unable to set groups to gid %u",
214                     (unsigned int)pw->pw_gid);
215                 return (-1);
216         }
217         if (setgid(pw->pw_gid) == -1) {
218                 pjdlog_errno(LOG_ERR, "Unable to set gid to %u",
219                     (unsigned int)pw->pw_gid);
220                 return (-1);
221         }
222         if (setuid(pw->pw_uid) == -1) {
223                 pjdlog_errno(LOG_ERR, "Unable to set uid to %u",
224                     (unsigned int)pw->pw_uid);
225                 return (-1);
226         }
227
228 #ifdef HAVE_CAPSICUM
229         capsicum = (cap_enter() == 0);
230         if (!capsicum) {
231                 pjdlog_common(LOG_DEBUG, 1, errno,
232                     "Unable to sandbox using capsicum");
233         } else if (res != NULL) {
234                 cap_rights_t rights;
235                 static const unsigned long geomcmds[] = {
236                     DIOCGDELETE,
237                     DIOCGFLUSH
238                 };
239
240                 PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY ||
241                     res->hr_role == HAST_ROLE_SECONDARY);
242
243                 cap_rights_init(&rights, CAP_FLOCK, CAP_IOCTL, CAP_PREAD,
244                     CAP_PWRITE);
245                 if (cap_rights_limit(res->hr_localfd, &rights) == -1) {
246                         pjdlog_errno(LOG_ERR,
247                             "Unable to limit capability rights on local descriptor");
248                 }
249                 if (cap_ioctls_limit(res->hr_localfd, geomcmds,
250                     sizeof(geomcmds) / sizeof(geomcmds[0])) == -1) {
251                         pjdlog_errno(LOG_ERR,
252                             "Unable to limit allowed GEOM ioctls");
253                 }
254
255                 if (res->hr_role == HAST_ROLE_PRIMARY) {
256                         static const unsigned long ggatecmds[] = {
257                             G_GATE_CMD_MODIFY,
258                             G_GATE_CMD_START,
259                             G_GATE_CMD_DONE,
260                             G_GATE_CMD_DESTROY
261                         };
262
263                         cap_rights_init(&rights, CAP_IOCTL);
264                         if (cap_rights_limit(res->hr_ggatefd, &rights) == -1) {
265                                 pjdlog_errno(LOG_ERR,
266                                     "Unable to limit capability rights to CAP_IOCTL on ggate descriptor");
267                         }
268                         if (cap_ioctls_limit(res->hr_ggatefd, ggatecmds,
269                             sizeof(ggatecmds) / sizeof(ggatecmds[0])) == -1) {
270                                 pjdlog_errno(LOG_ERR,
271                                     "Unable to limit allowed ggate ioctls");
272                         }
273                 }
274         }
275 #else
276         capsicum = false;
277 #endif
278
279         /*
280          * Better be sure that everything succeeded.
281          */
282         PJDLOG_VERIFY(getresuid(&ruid, &euid, &suid) == 0);
283         PJDLOG_VERIFY(ruid == pw->pw_uid);
284         PJDLOG_VERIFY(euid == pw->pw_uid);
285         PJDLOG_VERIFY(suid == pw->pw_uid);
286         PJDLOG_VERIFY(getresgid(&rgid, &egid, &sgid) == 0);
287         PJDLOG_VERIFY(rgid == pw->pw_gid);
288         PJDLOG_VERIFY(egid == pw->pw_gid);
289         PJDLOG_VERIFY(sgid == pw->pw_gid);
290         PJDLOG_VERIFY(getgroups(0, NULL) == 1);
291         PJDLOG_VERIFY(getgroups(1, gidset) == 1);
292         PJDLOG_VERIFY(gidset[0] == pw->pw_gid);
293
294         pjdlog_debug(1,
295             "Privileges successfully dropped using %s%s+setgid+setuid.",
296             capsicum ? "capsicum+" : "", jailed ? "jail" : "chroot");
297
298         return (0);
299 }