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