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