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