xref: /freebsd/sbin/bectl/bectl_jail.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
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
63 jailparam_add(const char *name, const char *val)
64 {
65 
66 	nvlist_add_string(jailparams, name, val);
67 }
68 
69 static int
70 jailparam_del(const char *name)
71 {
72 
73 	nvlist_remove_all(jailparams, name);
74 	return (0);
75 }
76 
77 static bool
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
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
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
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
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
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
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
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