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