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