xref: /freebsd/sbin/bectl/bectl_jail.c (revision 6044931335ca0fe7ecf61d2dcebc520c7a557ce6)
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 void jailparam_del(const char *name);
48 static bool jailparam_addarg(char *arg);
49 static bool 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 + 1];
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 void
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 	/* Not found... technically successful */
107 	if (i == jpused)
108 		return;
109 
110 	for (; i < jpused - 1; ++i) {
111 		val = jailparam_export(&jp[i + 1]);
112 
113 		jailparam_free(&jp[i], 1);
114 		jailparam_init(&jp[i], jp[i + 1].jp_name);
115 		jailparam_import(&jp[i], val);
116 		free(val);
117 	}
118 
119 	jailparam_free(&jp[i], 1);
120 	--jpused;
121 }
122 
123 static bool
124 jailparam_addarg(char *arg)
125 {
126 	char *name, *val;
127 
128 	if (arg == NULL)
129 		return (false);
130 	name = arg;
131 	if ((val = strchr(arg, '=')) == NULL) {
132 		fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
133 		    arg);
134 		return (false);
135 	}
136 
137 	*val++ = '\0';
138 	if (strcmp(name, "path") == 0) {
139 		if (strlen(val) > BE_MAXPATHLEN) {
140 			fprintf(stderr,
141 			    "bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
142 			    val, BE_MAXPATHLEN);
143 			return (false);
144 		}
145 		strcpy(mnt_loc, val);
146 	}
147 	jailparam_add(name, val);
148 	return (true);
149 }
150 
151 static bool
152 jailparam_delarg(char *arg)
153 {
154 	char *name, *val;
155 
156 	if (arg == NULL)
157 		return (false);
158 	name = arg;
159 	if ((val = strchr(name, '=')) != NULL)
160 		*val++ = '\0';
161 
162 	if (strcmp(name, "path") == 0)
163 		*mnt_loc = '\0';
164 	jailparam_del(name);
165 	return (true);
166 }
167 
168 int
169 bectl_cmd_jail(int argc, char *argv[])
170 {
171 	char *bootenv, *mountpoint;
172 	int jid, opt;
173 	bool default_hostname, default_name;
174 
175 	default_hostname = default_name = true;
176 	jpcnt = INIT_PARAMCOUNT;
177 	jp = malloc(jpcnt * sizeof(*jp));
178 	if (jp == NULL)
179 		err(2, "malloc");
180 
181 	jailparam_add("persist", "true");
182 	jailparam_add("allow.mount", "true");
183 	jailparam_add("allow.mount.devfs", "true");
184 	jailparam_add("enforce_statfs", "1");
185 
186 	while ((opt = getopt(argc, argv, "o:u:")) != -1) {
187 		switch (opt) {
188 		case 'o':
189 			if (jailparam_addarg(optarg)) {
190 				/*
191 				 * optarg has been modified to null terminate
192 				 * at the assignment operator.
193 				 */
194 				if (strcmp(optarg, "name") == 0)
195 					default_name = false;
196 				if (strcmp(optarg, "host.hostname") == 0)
197 					default_hostname = false;
198 			}
199 			break;
200 		case 'u':
201 			if (jailparam_delarg(optarg)) {
202 				if (strcmp(optarg, "name") == 0)
203 					default_name = true;
204 				if (strcmp(optarg, "host.hostname") == 0)
205 					default_hostname = true;
206 			}
207 			break;
208 		default:
209 			fprintf(stderr, "bectl jail: unknown option '-%c'\n",
210 			    optopt);
211 			return (usage(false));
212 		}
213 	}
214 
215 	argc -= optind;
216 	argv += optind;
217 
218 	/* struct jail be_jail = { 0 }; */
219 	if (argc < 1) {
220 		fprintf(stderr, "bectl jail: missing boot environment name\n");
221 		return (usage(false));
222 	}
223 	if (argc > 2) {
224 		fprintf(stderr, "bectl jail: too many arguments\n");
225 		return (usage(false));
226 	}
227 
228 	bootenv = argv[0];
229 
230 	/*
231 	 * XXX TODO: if its already mounted, perhaps there should be a flag to
232 	 * indicate its okay to proceed??
233 	 */
234 	if (*mnt_loc == '\0')
235 		mountpoint = NULL;
236 	else
237 		mountpoint = mnt_loc;
238 	if (be_mount(be, bootenv, mountpoint, 0, mnt_loc) != BE_ERR_SUCCESS) {
239 		fprintf(stderr, "could not mount bootenv\n");
240 		return (1);
241 	}
242 
243 	if (default_name)
244 		jailparam_add("name", bootenv);
245 	if (default_hostname)
246 		jailparam_add("host.hostname", bootenv);
247 	/*
248 	 * This is our indicator that path was not set by the user, so we'll use
249 	 * the path that libbe generated for us.
250 	 */
251 	if (mountpoint == NULL)
252 		jailparam_add("path", mnt_loc);
253 	jid = jailparam_set(jp, jpused, JAIL_CREATE | JAIL_ATTACH);
254 	if (jid == -1) {
255 		fprintf(stderr, "unable to create jail.  error: %d\n", errno);
256 		return (1);
257 	}
258 
259 	jailparam_free(jp, jpused);
260 	free(jp);
261 
262 	/* We're attached within the jail... good bye! */
263 	chdir("/");
264 	execl("/bin/sh", "/bin/sh", NULL);
265 	return (0);
266 }
267 
268 static int
269 bectl_search_jail_paths(const char *mnt)
270 {
271 	char jailpath[MAXPATHLEN + 1];
272 	int jid;
273 
274 	jid = 0;
275 	(void)mnt;
276 	while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath,
277 	    NULL)) != -1) {
278 		if (strcmp(jailpath, mnt) == 0)
279 			return (jid);
280 	}
281 
282 	return (-1);
283 }
284 
285 /*
286  * Locate a jail based on an arbitrary identifier.  This may be either a name,
287  * a jid, or a BE name.  Returns the jid or -1 on failure.
288  */
289 static int
290 bectl_locate_jail(const char *ident)
291 {
292 	nvlist_t *belist, *props;
293 	char *mnt;
294 	int jid;
295 
296 	/* Try the easy-match first */
297 	jid = jail_getid(ident);
298 	if (jid != -1)
299 		return (jid);
300 
301 	/* Attempt to try it as a BE name, first */
302 	if (be_prop_list_alloc(&belist) != 0)
303 		return (-1);
304 
305 	if (be_get_bootenv_props(be, belist) != 0)
306 		return (-1);
307 
308 	if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
309 		/* We'll attempt to resolve the jid by way of mountpoint */
310 		if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) {
311 			jid = bectl_search_jail_paths(mnt);
312 			be_prop_list_free(belist);
313 			return (jid);
314 		}
315 
316 		be_prop_list_free(belist);
317 	}
318 
319 	return (-1);
320 }
321 
322 int
323 bectl_cmd_unjail(int argc, char *argv[])
324 {
325 	char path[MAXPATHLEN + 1];
326 	char *cmd, *name, *target;
327 	int jid;
328 
329 	/* Store alias used */
330 	cmd = argv[0];
331 
332 	if (argc != 2) {
333 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
334 		return (usage(false));
335 	}
336 
337 	target = argv[1];
338 
339 	/* Locate the jail */
340 	if ((jid = bectl_locate_jail(target)) == -1) {
341 		fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
342 		    target);
343 		return (1);
344 	}
345 
346 	bzero(&path, MAXPATHLEN + 1);
347 	name = jail_getname(jid);
348 	if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
349 		free(name);
350 		fprintf(stderr,
351 		    "bectl %s: failed to get path for jail requested by '%s'\n",
352 		    cmd, target);
353 		return (1);
354 	}
355 
356 	free(name);
357 
358 	if (be_mounted_at(be, path, NULL) != 0) {
359 		fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
360 		    cmd, target);
361 		return (1);
362 	}
363 
364 	jail_remove(jid);
365 	unmount(path, 0);
366 
367 	return (0);
368 }
369