bectl_jail.c revision 350339
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: stable/11/sbin/bectl/bectl_jail.c 350339 2019-07-26 01:38:24Z kevans $");
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
47static void jailparam_add(const char *name, const char *val);
48static int jailparam_del(const char *name);
49static bool jailparam_addarg(char *arg);
50static int jailparam_delarg(char *arg);
51
52static int bectl_search_jail_paths(const char *mnt);
53static int bectl_locate_jail(const char *ident);
54static int bectl_jail_cleanup(char *mountpoint, int jid);
55
56static char mnt_loc[BE_MAXPATHLEN];
57static nvlist_t *jailparams;
58
59static const char *disabled_params[] = {
60    "command", "exec.start", "nopersist", "persist", NULL
61};
62
63
64static void
65jailparam_add(const char *name, const char *val)
66{
67
68	nvlist_add_string(jailparams, name, val);
69}
70
71static int
72jailparam_del(const char *name)
73{
74
75	nvlist_remove_all(jailparams, name);
76	return (0);
77}
78
79static bool
80jailparam_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
117static int
118jailparam_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
133static int
134build_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
198error:
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. */
209static int
210bectl_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
237int
238bectl_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	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	if (!interactive)
359		return (0);
360
361	if (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
376static int
377bectl_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 */
404static int
405bectl_locate_jail(const char *ident)
406{
407	nvlist_t *belist, *props;
408	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
445int
446bectl_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