xref: /freebsd/sbin/bectl/bectl_jail.c (revision 0957b409)
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 
44 #include "bectl.h"
45 
46 static void jailparam_grow(void);
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 
55 /* We'll start with 8 parameters initially and grow as needed. */
56 #define	INIT_PARAMCOUNT	8
57 
58 static struct jailparam *jp;
59 static int jpcnt;
60 static int jpused;
61 static char mnt_loc[BE_MAXPATHLEN];
62 
63 static void
64 jailparam_grow(void)
65 {
66 
67 	jpcnt *= 2;
68 	jp = realloc(jp, jpcnt * sizeof(*jp));
69 	if (jp == NULL)
70 		err(2, "realloc");
71 }
72 
73 static void
74 jailparam_add(const char *name, const char *val)
75 {
76 	int i;
77 
78 	for (i = 0; i < jpused; ++i) {
79 		if (strcmp(name, jp[i].jp_name) == 0)
80 			break;
81 	}
82 
83 	if (i < jpused)
84 		jailparam_free(&jp[i], 1);
85 	else if (jpused == jpcnt)
86 		/* The next slot isn't allocated yet */
87 		jailparam_grow();
88 
89 	if (jailparam_init(&jp[i], name) != 0)
90 		return;
91 	if (jailparam_import(&jp[i], val) != 0)
92 		return;
93 	++jpused;
94 }
95 
96 static int
97 jailparam_del(const char *name)
98 {
99 	int i;
100 	char *val;
101 
102 	for (i = 0; i < jpused; ++i) {
103 		if (strcmp(name, jp[i].jp_name) == 0)
104 			break;
105 	}
106 
107 	if (i == jpused)
108 		return (ENOENT);
109 
110 	for (; i < jpused - 1; ++i) {
111 		val = jailparam_export(&jp[i + 1]);
112 
113 		jailparam_free(&jp[i], 1);
114 		/*
115 		 * Given the context, the following will really only fail if
116 		 * they can't allocate the copy of the name or value.
117 		 */
118 		if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) {
119 			free(val);
120 			return (ENOMEM);
121 		}
122 		if (jailparam_import(&jp[i], val) != 0) {
123 			jailparam_free(&jp[i], 1);
124 			free(val);
125 			return (ENOMEM);
126 		}
127 		free(val);
128 	}
129 
130 	jailparam_free(&jp[i], 1);
131 	--jpused;
132 	return (0);
133 }
134 
135 static bool
136 jailparam_addarg(char *arg)
137 {
138 	char *name, *val;
139 
140 	if (arg == NULL)
141 		return (false);
142 	name = arg;
143 	if ((val = strchr(arg, '=')) == NULL) {
144 		fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
145 		    arg);
146 		return (false);
147 	}
148 
149 	*val++ = '\0';
150 	if (strcmp(name, "path") == 0) {
151 		if (strlen(val) >= BE_MAXPATHLEN) {
152 			fprintf(stderr,
153 			    "bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
154 			    val, BE_MAXPATHLEN);
155 			return (false);
156 		}
157 		strlcpy(mnt_loc, val, sizeof(mnt_loc));
158 	}
159 	jailparam_add(name, val);
160 	return (true);
161 }
162 
163 static int
164 jailparam_delarg(char *arg)
165 {
166 	char *name, *val;
167 
168 	if (arg == NULL)
169 		return (EINVAL);
170 	name = arg;
171 	if ((val = strchr(name, '=')) != NULL)
172 		*val++ = '\0';
173 
174 	if (strcmp(name, "path") == 0)
175 		*mnt_loc = '\0';
176 	return (jailparam_del(name));
177 }
178 
179 int
180 bectl_cmd_jail(int argc, char *argv[])
181 {
182 	char *bootenv, *mountpoint;
183 	int jid, mntflags, opt, ret;
184 	bool default_hostname, interactive, unjail;
185 	pid_t pid;
186 
187 	/* XXX TODO: Allow shallow */
188 	mntflags = BE_MNT_DEEP;
189 	default_hostname = interactive = unjail = true;
190 	jpcnt = INIT_PARAMCOUNT;
191 	jp = malloc(jpcnt * sizeof(*jp));
192 	if (jp == NULL)
193 		err(2, "malloc");
194 
195 	jailparam_add("persist", "true");
196 	jailparam_add("allow.mount", "true");
197 	jailparam_add("allow.mount.devfs", "true");
198 	jailparam_add("enforce_statfs", "1");
199 
200 	while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) {
201 		switch (opt) {
202 		case 'b':
203 			interactive = false;
204 			break;
205 		case 'o':
206 			if (jailparam_addarg(optarg)) {
207 				/*
208 				 * optarg has been modified to null terminate
209 				 * at the assignment operator.
210 				 */
211 				if (strcmp(optarg, "host.hostname") == 0)
212 					default_hostname = false;
213 			}
214 			break;
215 		case 'U':
216 			unjail = false;
217 			break;
218 		case 'u':
219 			if ((ret = jailparam_delarg(optarg)) == 0) {
220 				if (strcmp(optarg, "host.hostname") == 0)
221 					default_hostname = true;
222 			} else if (ret != ENOENT) {
223 				fprintf(stderr,
224 				    "bectl jail: error unsetting \"%s\"\n",
225 				    optarg);
226 				return (ret);
227 			}
228 			break;
229 		default:
230 			fprintf(stderr, "bectl jail: unknown option '-%c'\n",
231 			    optopt);
232 			return (usage(false));
233 		}
234 	}
235 
236 	argc -= optind;
237 	argv += optind;
238 
239 	/* struct jail be_jail = { 0 }; */
240 	if (argc < 1) {
241 		fprintf(stderr, "bectl jail: missing boot environment name\n");
242 		return (usage(false));
243 	}
244 
245 	bootenv = argv[0];
246 
247 	/*
248 	 * XXX TODO: if its already mounted, perhaps there should be a flag to
249 	 * indicate its okay to proceed??
250 	 */
251 	if (*mnt_loc == '\0')
252 		mountpoint = NULL;
253 	else
254 		mountpoint = mnt_loc;
255 	if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) {
256 		fprintf(stderr, "could not mount bootenv\n");
257 		return (1);
258 	}
259 
260 	if (default_hostname)
261 		jailparam_add("host.hostname", bootenv);
262 
263 	/*
264 	 * This is our indicator that path was not set by the user, so we'll use
265 	 * the path that libbe generated for us.
266 	 */
267 	if (mountpoint == NULL)
268 		jailparam_add("path", mnt_loc);
269 	/* Create the jail for now, attach later as-needed */
270 	jid = jailparam_set(jp, jpused, JAIL_CREATE);
271 	if (jid == -1) {
272 		fprintf(stderr, "unable to create jail.  error: %d\n", errno);
273 		return (1);
274 	}
275 
276 	jailparam_free(jp, jpused);
277 	free(jp);
278 
279 	/* We're not interactive, nothing more to do here. */
280 	if (!interactive)
281 		return (0);
282 
283 	pid = fork();
284 	switch(pid) {
285 	case -1:
286 		perror("fork");
287 		return (1);
288 	case 0:
289 		jail_attach(jid);
290 		/* We're attached within the jail... good bye! */
291 		chdir("/");
292 		if (argc > 1)
293 			execve(argv[1], &argv[1], NULL);
294 		else
295 			execl("/bin/sh", "/bin/sh", NULL);
296 		fprintf(stderr, "bectl jail: failed to execute %s\n",
297 		    (argc > 1 ? argv[1] : "/bin/sh"));
298 		_exit(1);
299 	default:
300 		/* Wait for the child to get back, see if we need to unjail */
301 		waitpid(pid, NULL, 0);
302 	}
303 
304 	if (unjail) {
305 		jail_remove(jid);
306 		be_unmount(be, bootenv, 0);
307 	}
308 
309 	return (0);
310 }
311 
312 static int
313 bectl_search_jail_paths(const char *mnt)
314 {
315 	int jid;
316 	char lastjid[16];
317 	char jailpath[MAXPATHLEN];
318 
319 	/* jail_getv expects name/value strings */
320 	snprintf(lastjid, sizeof(lastjid), "%d", 0);
321 
322 	jid = 0;
323 	while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath,
324 	    NULL)) != -1) {
325 
326 		/* the jail we've been looking for */
327 		if (strcmp(jailpath, mnt) == 0)
328 			return (jid);
329 
330 		/* update lastjid and keep on looking */
331 		snprintf(lastjid, sizeof(lastjid), "%d", jid);
332 	}
333 
334 	return (-1);
335 }
336 
337 /*
338  * Locate a jail based on an arbitrary identifier.  This may be either a name,
339  * a jid, or a BE name.  Returns the jid or -1 on failure.
340  */
341 static int
342 bectl_locate_jail(const char *ident)
343 {
344 	nvlist_t *belist, *props;
345 	char *mnt;
346 	int jid;
347 
348 	/* Try the easy-match first */
349 	jid = jail_getid(ident);
350 	if (jid != -1)
351 		return (jid);
352 
353 	/* Attempt to try it as a BE name, first */
354 	if (be_prop_list_alloc(&belist) != 0)
355 		return (-1);
356 
357 	if (be_get_bootenv_props(be, belist) != 0)
358 		return (-1);
359 
360 	if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
361 
362 		/* path where a boot environment is mounted */
363 		if (nvlist_lookup_string(props, "mounted", &mnt) == 0) {
364 
365 			/* looking for a jail that matches our bootenv path */
366 			jid = bectl_search_jail_paths(mnt);
367 			be_prop_list_free(belist);
368 			return (jid);
369 		}
370 
371 		be_prop_list_free(belist);
372 	}
373 
374 	return (-1);
375 }
376 
377 int
378 bectl_cmd_unjail(int argc, char *argv[])
379 {
380 	char path[MAXPATHLEN];
381 	char *cmd, *name, *target;
382 	int jid;
383 
384 	/* Store alias used */
385 	cmd = argv[0];
386 
387 	if (argc != 2) {
388 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
389 		return (usage(false));
390 	}
391 
392 	target = argv[1];
393 
394 	/* Locate the jail */
395 	if ((jid = bectl_locate_jail(target)) == -1) {
396 		fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
397 		    target);
398 		return (1);
399 	}
400 
401 	bzero(&path, MAXPATHLEN);
402 	name = jail_getname(jid);
403 	if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
404 		free(name);
405 		fprintf(stderr,
406 		    "bectl %s: failed to get path for jail requested by '%s'\n",
407 		    cmd, target);
408 		return (1);
409 	}
410 
411 	free(name);
412 
413 	if (be_mounted_at(be, path, NULL) != 0) {
414 		fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
415 		    cmd, target);
416 		return (1);
417 	}
418 
419 	jail_remove(jid);
420 	be_unmount(be, target, 0);
421 
422 	return (0);
423 }
424