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