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