xref: /freebsd/sbin/bectl/bectl_jail.c (revision 6be3386466ab79a84b48429ae66244f21526d3df)
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 	/* 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
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
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
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