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 41 jailparam_add(const char *name, const char *val) 42 { 43 44 nvlist_add_string(jailparams, name, val); 45 } 46 47 static int 48 jailparam_del(const char *name) 49 { 50 51 nvlist_remove_all(jailparams, name); 52 return (0); 53 } 54 55 static bool 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 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 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 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 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 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 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 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