1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/param.h>
29 #include <sys/jail.h>
30 #include <sys/mount.h>
31 #include <sys/wait.h>
32 #include <err.h>
33 #include <jail.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 #include <be.h>
40 #include "bectl.h"
41
42 #define MNTTYPE_ZFS 222
43
44 static void jailparam_add(const char *name, const char *val);
45 static int jailparam_del(const char *name);
46 static bool jailparam_addarg(char *arg);
47 static int jailparam_delarg(char *arg);
48
49 static int bectl_search_jail_paths(const char *mnt);
50 static int bectl_locate_jail(const char *ident);
51 static int bectl_jail_cleanup(char *mountpoint, int jid);
52
53 static char mnt_loc[BE_MAXPATHLEN];
54 static nvlist_t *jailparams;
55
56 static const char *disabled_params[] = {
57 "command", "exec.start", "nopersist", "persist", NULL
58 };
59
60
61 static void
jailparam_add(const char * name,const char * val)62 jailparam_add(const char *name, const char *val)
63 {
64
65 nvlist_add_string(jailparams, name, val);
66 }
67
68 static int
jailparam_del(const char * name)69 jailparam_del(const char *name)
70 {
71
72 nvlist_remove_all(jailparams, name);
73 return (0);
74 }
75
76 static bool
jailparam_addarg(char * arg)77 jailparam_addarg(char *arg)
78 {
79 char *name, *val;
80 size_t i, len;
81
82 if (arg == NULL)
83 return (false);
84 name = arg;
85 if ((val = strchr(arg, '=')) == NULL) {
86 fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
87 arg);
88 return (false);
89 }
90
91 *val++ = '\0';
92 if (strcmp(name, "path") == 0) {
93 if (strlen(val) >= BE_MAXPATHLEN) {
94 fprintf(stderr,
95 "bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
96 val, BE_MAXPATHLEN);
97 return (false);
98 }
99 strlcpy(mnt_loc, val, sizeof(mnt_loc));
100 }
101
102 for (i = 0; disabled_params[i] != NULL; i++) {
103 len = strlen(disabled_params[i]);
104 if (strncmp(disabled_params[i], name, len) == 0) {
105 fprintf(stderr, "invalid jail parameter: %s\n", name);
106 return (false);
107 }
108 }
109
110 jailparam_add(name, val);
111 return (true);
112 }
113
114 static int
jailparam_delarg(char * arg)115 jailparam_delarg(char *arg)
116 {
117 char *name, *val;
118
119 if (arg == NULL)
120 return (EINVAL);
121 name = arg;
122 if ((val = strchr(name, '=')) != NULL)
123 *val++ = '\0';
124
125 if (strcmp(name, "path") == 0)
126 *mnt_loc = '\0';
127 return (jailparam_del(name));
128 }
129
130 static int
build_jailcmd(char *** argvp,bool interactive,int argc,char * argv[])131 build_jailcmd(char ***argvp, bool interactive, int argc, char *argv[])
132 {
133 char *cmd, **jargv;
134 const char *name, *val;
135 nvpair_t *nvp;
136 size_t i, iarg, nargv;
137
138 cmd = NULL;
139 nvp = NULL;
140 iarg = i = 0;
141 if (nvlist_size(jailparams, &nargv, NV_ENCODE_NATIVE) != 0)
142 return (1);
143
144 /*
145 * Number of args + "/usr/sbin/jail", "-c", and ending NULL.
146 * If interactive also include command.
147 */
148 nargv += 3;
149 if (interactive) {
150 if (argc == 0)
151 nargv++;
152 else
153 nargv += argc;
154 }
155
156 jargv = *argvp = calloc(nargv, sizeof(*jargv));
157 if (jargv == NULL)
158 err(2, "calloc");
159
160 jargv[iarg++] = strdup("/usr/sbin/jail");
161 jargv[iarg++] = strdup("-c");
162 while ((nvp = nvlist_next_nvpair(jailparams, nvp)) != NULL) {
163 name = nvpair_name(nvp);
164 if (nvpair_value_string(nvp, &val) != 0)
165 continue;
166
167 if (asprintf(&jargv[iarg++], "%s=%s", name, val) < 0)
168 goto error;
169 }
170 if (interactive) {
171 if (argc < 1)
172 cmd = strdup("/bin/sh");
173 else {
174 cmd = argv[0];
175 argc--;
176 argv++;
177 }
178
179 if (asprintf(&jargv[iarg++], "command=%s", cmd) < 0) {
180 goto error;
181 }
182 if (argc < 1) {
183 free(cmd);
184 cmd = NULL;
185 }
186
187 for (; argc > 0; argc--) {
188 if (asprintf(&jargv[iarg++], "%s", argv[0]) < 0)
189 goto error;
190 argv++;
191 }
192 }
193
194 return (0);
195
196 error:
197 if (interactive && argc < 1)
198 free(cmd);
199 for (; i < iarg - 1; i++) {
200 free(jargv[i]);
201 }
202 free(jargv);
203 return (1);
204 }
205
206 /* Remove jail and cleanup any non zfs mounts. */
207 static int
bectl_jail_cleanup(char * mountpoint,int jid)208 bectl_jail_cleanup(char *mountpoint, int jid)
209 {
210 struct statfs *mntbuf;
211 size_t i, searchlen, mntsize;
212
213 if (jid >= 0 && jail_remove(jid) != 0) {
214 fprintf(stderr, "unable to remove jail");
215 return (1);
216 }
217
218 searchlen = strnlen(mountpoint, MAXPATHLEN);
219 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
220 for (i = 0; i < mntsize; i++) {
221 if (strncmp(mountpoint, mntbuf[i].f_mntonname, searchlen) == 0 &&
222 mntbuf[i].f_type != MNTTYPE_ZFS) {
223
224 if (unmount(mntbuf[i].f_mntonname, 0) != 0) {
225 fprintf(stderr, "bectl jail: unable to unmount filesystem %s",
226 mntbuf[i].f_mntonname);
227 return (1);
228 }
229 }
230 }
231
232 return (0);
233 }
234
235 int
bectl_cmd_jail(int argc,char * argv[])236 bectl_cmd_jail(int argc, char *argv[])
237 {
238 char *bootenv, **jargv, *mountpoint;
239 int i, jid, mntflags, opt, ret;
240 bool default_hostname, interactive, unjail;
241 pid_t pid;
242
243
244 /* XXX TODO: Allow shallow */
245 mntflags = BE_MNT_DEEP;
246 default_hostname = interactive = unjail = true;
247
248 if ((nvlist_alloc(&jailparams, NV_UNIQUE_NAME, 0)) != 0) {
249 fprintf(stderr, "nvlist_alloc() failed\n");
250 return (1);
251 }
252
253 jailparam_add("persist", "true");
254 jailparam_add("allow.mount", "true");
255 jailparam_add("allow.mount.devfs", "true");
256 jailparam_add("enforce_statfs", "1");
257
258 while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) {
259 switch (opt) {
260 case 'b':
261 interactive = false;
262 break;
263 case 'o':
264 if (jailparam_addarg(optarg)) {
265 /*
266 * optarg has been modified to null terminate
267 * at the assignment operator.
268 */
269 if (strcmp(optarg, "host.hostname") == 0)
270 default_hostname = false;
271 } else {
272 return (1);
273 }
274 break;
275 case 'U':
276 unjail = false;
277 break;
278 case 'u':
279 if ((ret = jailparam_delarg(optarg)) == 0) {
280 if (strcmp(optarg, "host.hostname") == 0)
281 default_hostname = true;
282 } else if (ret != ENOENT) {
283 fprintf(stderr,
284 "bectl jail: error unsetting \"%s\"\n",
285 optarg);
286 return (ret);
287 }
288 break;
289 default:
290 fprintf(stderr, "bectl jail: unknown option '-%c'\n",
291 optopt);
292 return (usage(false));
293 }
294 }
295
296 argc -= optind;
297 argv += optind;
298
299 if (argc < 1) {
300 fprintf(stderr, "bectl jail: missing boot environment name\n");
301 return (usage(false));
302 }
303
304 bootenv = argv[0];
305 argc--;
306 argv++;
307
308 /*
309 * XXX TODO: if its already mounted, perhaps there should be a flag to
310 * indicate its okay to proceed??
311 */
312 if (*mnt_loc == '\0')
313 mountpoint = NULL;
314 else
315 mountpoint = mnt_loc;
316 if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) {
317 fprintf(stderr, "could not mount bootenv\n");
318 return (1);
319 }
320
321 if (default_hostname)
322 jailparam_add("host.hostname", bootenv);
323
324 /*
325 * This is our indicator that path was not set by the user, so we'll use
326 * the path that libbe generated for us.
327 */
328 if (mountpoint == NULL) {
329 jailparam_add("path", mnt_loc);
330 mountpoint = mnt_loc;
331 }
332
333 if ((build_jailcmd(&jargv, interactive, argc, argv)) != 0) {
334 fprintf(stderr, "unable to build argument list for jail command\n");
335 return (1);
336 }
337
338 pid = fork();
339
340 switch (pid) {
341 case -1:
342 perror("fork");
343 return (1);
344 case 0:
345 execv("/usr/sbin/jail", jargv);
346 fprintf(stderr, "bectl jail: failed to execute\n");
347 return (1);
348 default:
349 waitpid(pid, NULL, 0);
350 }
351
352 for (i = 0; jargv[i] != NULL; i++) {
353 free(jargv[i]);
354 }
355 free(jargv);
356
357 /* Non-interactive (-b) mode means the jail sticks around. */
358 if (interactive && unjail) {
359 /*
360 * We're not checking the jail id result here because in the
361 * case of invalid param, or last command in jail was an error
362 * the jail will not exist upon exit. bectl_jail_cleanup will
363 * only jail_remove if the jid is >= 0.
364 */
365 jid = bectl_locate_jail(bootenv);
366 bectl_jail_cleanup(mountpoint, jid);
367 be_unmount(be, bootenv, 0);
368 }
369
370 return (0);
371 }
372
373 static int
bectl_search_jail_paths(const char * mnt)374 bectl_search_jail_paths(const char *mnt)
375 {
376 int jid;
377 char lastjid[16];
378 char jailpath[MAXPATHLEN];
379
380 /* jail_getv expects name/value strings */
381 snprintf(lastjid, sizeof(lastjid), "%d", 0);
382
383 while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath,
384 NULL)) != -1) {
385
386 /* the jail we've been looking for */
387 if (strcmp(jailpath, mnt) == 0)
388 return (jid);
389
390 /* update lastjid and keep on looking */
391 snprintf(lastjid, sizeof(lastjid), "%d", jid);
392 }
393
394 return (-1);
395 }
396
397 /*
398 * Locate a jail based on an arbitrary identifier. This may be either a name,
399 * a jid, or a BE name. Returns the jid or -1 on failure.
400 */
401 static int
bectl_locate_jail(const char * ident)402 bectl_locate_jail(const char *ident)
403 {
404 nvlist_t *belist, *props;
405 const char *mnt;
406 int jid;
407
408 /* Try the easy-match first */
409 jid = jail_getid(ident);
410 /*
411 * jail_getid(0) will always return 0, because this prison does exist.
412 * bectl(8) knows that this is not what it wants, so we should fall
413 * back to mount point search.
414 */
415 if (jid > 0)
416 return (jid);
417
418 /* Attempt to try it as a BE name, first */
419 if (be_prop_list_alloc(&belist) != 0)
420 return (-1);
421
422 if (be_get_bootenv_props(be, belist) != 0)
423 return (-1);
424
425 if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
426
427 /* path where a boot environment is mounted */
428 if (nvlist_lookup_string(props, "mounted", &mnt) == 0) {
429
430 /* looking for a jail that matches our bootenv path */
431 jid = bectl_search_jail_paths(mnt);
432 be_prop_list_free(belist);
433 return (jid);
434 }
435
436 be_prop_list_free(belist);
437 }
438
439 return (-1);
440 }
441
442 int
bectl_cmd_unjail(int argc,char * argv[])443 bectl_cmd_unjail(int argc, char *argv[])
444 {
445 char path[MAXPATHLEN];
446 char *cmd, *name, *target;
447 int jid;
448
449 /* Store alias used */
450 cmd = argv[0];
451
452 if (argc != 2) {
453 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
454 return (usage(false));
455 }
456
457 target = argv[1];
458
459 /* Locate the jail */
460 if ((jid = bectl_locate_jail(target)) == -1) {
461 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
462 target);
463 return (1);
464 }
465
466 bzero(&path, MAXPATHLEN);
467 name = jail_getname(jid);
468 if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
469 free(name);
470 fprintf(stderr,
471 "bectl %s: failed to get path for jail requested by '%s'\n",
472 cmd, target);
473 return (1);
474 }
475
476 free(name);
477
478 if (be_mounted_at(be, path, NULL) != 0) {
479 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
480 cmd, target);
481 return (1);
482 }
483
484 bectl_jail_cleanup(path, jid);
485 be_unmount(be, target, 0);
486
487 return (0);
488 }
489