1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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; 137 const char *name, *val; 138 nvpair_t *nvp; 139 size_t i, iarg, nargv; 140 141 cmd = NULL; 142 nvp = NULL; 143 iarg = i = 0; 144 if (nvlist_size(jailparams, &nargv, NV_ENCODE_NATIVE) != 0) 145 return (1); 146 147 /* 148 * Number of args + "/usr/sbin/jail", "-c", and ending NULL. 149 * If interactive also include command. 150 */ 151 nargv += 3; 152 if (interactive) { 153 if (argc == 0) 154 nargv++; 155 else 156 nargv += argc; 157 } 158 159 jargv = *argvp = calloc(nargv, sizeof(*jargv)); 160 if (jargv == NULL) 161 err(2, "calloc"); 162 163 jargv[iarg++] = strdup("/usr/sbin/jail"); 164 jargv[iarg++] = strdup("-c"); 165 while ((nvp = nvlist_next_nvpair(jailparams, nvp)) != NULL) { 166 name = nvpair_name(nvp); 167 if (nvpair_value_string(nvp, &val) != 0) 168 continue; 169 170 if (asprintf(&jargv[iarg++], "%s=%s", name, val) < 0) 171 goto error; 172 } 173 if (interactive) { 174 if (argc < 1) 175 cmd = strdup("/bin/sh"); 176 else { 177 cmd = argv[0]; 178 argc--; 179 argv++; 180 } 181 182 if (asprintf(&jargv[iarg++], "command=%s", cmd) < 0) { 183 goto error; 184 } 185 if (argc < 1) { 186 free(cmd); 187 cmd = NULL; 188 } 189 190 for (; argc > 0; argc--) { 191 if (asprintf(&jargv[iarg++], "%s", argv[0]) < 0) 192 goto error; 193 argv++; 194 } 195 } 196 197 return (0); 198 199 error: 200 if (interactive && argc < 1) 201 free(cmd); 202 for (; i < iarg - 1; i++) { 203 free(jargv[i]); 204 } 205 free(jargv); 206 return (1); 207 } 208 209 /* Remove jail and cleanup any non zfs mounts. */ 210 static int 211 bectl_jail_cleanup(char *mountpoint, int jid) 212 { 213 struct statfs *mntbuf; 214 size_t i, searchlen, mntsize; 215 216 if (jid >= 0 && jail_remove(jid) != 0) { 217 fprintf(stderr, "unable to remove jail"); 218 return (1); 219 } 220 221 searchlen = strnlen(mountpoint, MAXPATHLEN); 222 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 223 for (i = 0; i < mntsize; i++) { 224 if (strncmp(mountpoint, mntbuf[i].f_mntonname, searchlen) == 0 && 225 mntbuf[i].f_type != MNTTYPE_ZFS) { 226 227 if (unmount(mntbuf[i].f_mntonname, 0) != 0) { 228 fprintf(stderr, "bectl jail: unable to unmount filesystem %s", 229 mntbuf[i].f_mntonname); 230 return (1); 231 } 232 } 233 } 234 235 return (0); 236 } 237 238 int 239 bectl_cmd_jail(int argc, char *argv[]) 240 { 241 char *bootenv, **jargv, *mountpoint; 242 int i, jid, mntflags, opt, ret; 243 bool default_hostname, interactive, unjail; 244 pid_t pid; 245 246 247 /* XXX TODO: Allow shallow */ 248 mntflags = BE_MNT_DEEP; 249 default_hostname = interactive = unjail = true; 250 251 if ((nvlist_alloc(&jailparams, NV_UNIQUE_NAME, 0)) != 0) { 252 fprintf(stderr, "nvlist_alloc() failed\n"); 253 return (1); 254 } 255 256 jailparam_add("persist", "true"); 257 jailparam_add("allow.mount", "true"); 258 jailparam_add("allow.mount.devfs", "true"); 259 jailparam_add("enforce_statfs", "1"); 260 261 while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) { 262 switch (opt) { 263 case 'b': 264 interactive = false; 265 break; 266 case 'o': 267 if (jailparam_addarg(optarg)) { 268 /* 269 * optarg has been modified to null terminate 270 * at the assignment operator. 271 */ 272 if (strcmp(optarg, "host.hostname") == 0) 273 default_hostname = false; 274 } else { 275 return (1); 276 } 277 break; 278 case 'U': 279 unjail = false; 280 break; 281 case 'u': 282 if ((ret = jailparam_delarg(optarg)) == 0) { 283 if (strcmp(optarg, "host.hostname") == 0) 284 default_hostname = true; 285 } else if (ret != ENOENT) { 286 fprintf(stderr, 287 "bectl jail: error unsetting \"%s\"\n", 288 optarg); 289 return (ret); 290 } 291 break; 292 default: 293 fprintf(stderr, "bectl jail: unknown option '-%c'\n", 294 optopt); 295 return (usage(false)); 296 } 297 } 298 299 argc -= optind; 300 argv += optind; 301 302 if (argc < 1) { 303 fprintf(stderr, "bectl jail: missing boot environment name\n"); 304 return (usage(false)); 305 } 306 307 bootenv = argv[0]; 308 argc--; 309 argv++; 310 311 /* 312 * XXX TODO: if its already mounted, perhaps there should be a flag to 313 * indicate its okay to proceed?? 314 */ 315 if (*mnt_loc == '\0') 316 mountpoint = NULL; 317 else 318 mountpoint = mnt_loc; 319 if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) { 320 fprintf(stderr, "could not mount bootenv\n"); 321 return (1); 322 } 323 324 if (default_hostname) 325 jailparam_add("host.hostname", bootenv); 326 327 /* 328 * This is our indicator that path was not set by the user, so we'll use 329 * the path that libbe generated for us. 330 */ 331 if (mountpoint == NULL) { 332 jailparam_add("path", mnt_loc); 333 mountpoint = mnt_loc; 334 } 335 336 if ((build_jailcmd(&jargv, interactive, argc, argv)) != 0) { 337 fprintf(stderr, "unable to build argument list for jail command\n"); 338 return (1); 339 } 340 341 pid = fork(); 342 343 switch (pid) { 344 case -1: 345 perror("fork"); 346 return (1); 347 case 0: 348 execv("/usr/sbin/jail", jargv); 349 fprintf(stderr, "bectl jail: failed to execute\n"); 350 return (1); 351 default: 352 waitpid(pid, NULL, 0); 353 } 354 355 for (i = 0; jargv[i] != NULL; i++) { 356 free(jargv[i]); 357 } 358 free(jargv); 359 360 /* Non-interactive (-b) mode means the jail sticks around. */ 361 if (interactive && 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 const 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 } 492