xref: /freebsd/sbin/bectl/bectl_jail.c (revision c697fb7f)
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, *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
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
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 	if (!interactive)
360 		return (0);
361 
362 	if (unjail) {
363 		/*
364 		 *  We're not checking the jail id result here because in the
365 		 *  case of invalid param, or last command in jail was an error
366 		 *  the jail will not exist upon exit. bectl_jail_cleanup will
367 		 *  only jail_remove if the jid is >= 0.
368 		 */
369 		jid = bectl_locate_jail(bootenv);
370 		bectl_jail_cleanup(mountpoint, jid);
371 		be_unmount(be, bootenv, 0);
372 	}
373 
374 	return (0);
375 }
376 
377 static int
378 bectl_search_jail_paths(const char *mnt)
379 {
380 	int jid;
381 	char lastjid[16];
382 	char jailpath[MAXPATHLEN];
383 
384 	/* jail_getv expects name/value strings */
385 	snprintf(lastjid, sizeof(lastjid), "%d", 0);
386 
387 	while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath,
388 	    NULL)) != -1) {
389 
390 		/* the jail we've been looking for */
391 		if (strcmp(jailpath, mnt) == 0)
392 			return (jid);
393 
394 		/* update lastjid and keep on looking */
395 		snprintf(lastjid, sizeof(lastjid), "%d", jid);
396 	}
397 
398 	return (-1);
399 }
400 
401 /*
402  * Locate a jail based on an arbitrary identifier.  This may be either a name,
403  * a jid, or a BE name.  Returns the jid or -1 on failure.
404  */
405 static int
406 bectl_locate_jail(const char *ident)
407 {
408 	nvlist_t *belist, *props;
409 	char *mnt;
410 	int jid;
411 
412 	/* Try the easy-match first */
413 	jid = jail_getid(ident);
414 	/*
415 	 * jail_getid(0) will always return 0, because this prison does exist.
416 	 * bectl(8) knows that this is not what it wants, so we should fall
417 	 * back to mount point search.
418 	 */
419 	if (jid > 0)
420 		return (jid);
421 
422 	/* Attempt to try it as a BE name, first */
423 	if (be_prop_list_alloc(&belist) != 0)
424 		return (-1);
425 
426 	if (be_get_bootenv_props(be, belist) != 0)
427 		return (-1);
428 
429 	if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
430 
431 		/* path where a boot environment is mounted */
432 		if (nvlist_lookup_string(props, "mounted", &mnt) == 0) {
433 
434 			/* looking for a jail that matches our bootenv path */
435 			jid = bectl_search_jail_paths(mnt);
436 			be_prop_list_free(belist);
437 			return (jid);
438 		}
439 
440 		be_prop_list_free(belist);
441 	}
442 
443 	return (-1);
444 }
445 
446 int
447 bectl_cmd_unjail(int argc, char *argv[])
448 {
449 	char path[MAXPATHLEN];
450 	char *cmd, *name, *target;
451 	int jid;
452 
453 	/* Store alias used */
454 	cmd = argv[0];
455 
456 	if (argc != 2) {
457 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
458 		return (usage(false));
459 	}
460 
461 	target = argv[1];
462 
463 	/* Locate the jail */
464 	if ((jid = bectl_locate_jail(target)) == -1) {
465 		fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
466 		    target);
467 		return (1);
468 	}
469 
470 	bzero(&path, MAXPATHLEN);
471 	name = jail_getname(jid);
472 	if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
473 		free(name);
474 		fprintf(stderr,
475 		    "bectl %s: failed to get path for jail requested by '%s'\n",
476 		    cmd, target);
477 		return (1);
478 	}
479 
480 	free(name);
481 
482 	if (be_mounted_at(be, path, NULL) != 0) {
483 		fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
484 		    cmd, target);
485 		return (1);
486 	}
487 
488 	bectl_jail_cleanup(path, jid);
489 	be_unmount(be, target, 0);
490 
491 	return (0);
492 }
493