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