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 44 #include "bectl.h" 45 46 static void jailparam_grow(void); 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 55 /* We'll start with 8 parameters initially and grow as needed. */ 56 #define INIT_PARAMCOUNT 8 57 58 static struct jailparam *jp; 59 static int jpcnt; 60 static int jpused; 61 static char mnt_loc[BE_MAXPATHLEN]; 62 63 static void 64 jailparam_grow(void) 65 { 66 67 jpcnt *= 2; 68 jp = realloc(jp, jpcnt * sizeof(*jp)); 69 if (jp == NULL) 70 err(2, "realloc"); 71 } 72 73 static void 74 jailparam_add(const char *name, const char *val) 75 { 76 int i; 77 78 for (i = 0; i < jpused; ++i) { 79 if (strcmp(name, jp[i].jp_name) == 0) 80 break; 81 } 82 83 if (i < jpused) 84 jailparam_free(&jp[i], 1); 85 else if (jpused == jpcnt) 86 /* The next slot isn't allocated yet */ 87 jailparam_grow(); 88 89 if (jailparam_init(&jp[i], name) != 0) 90 return; 91 if (jailparam_import(&jp[i], val) != 0) 92 return; 93 ++jpused; 94 } 95 96 static int 97 jailparam_del(const char *name) 98 { 99 int i; 100 char *val; 101 102 for (i = 0; i < jpused; ++i) { 103 if (strcmp(name, jp[i].jp_name) == 0) 104 break; 105 } 106 107 if (i == jpused) 108 return (ENOENT); 109 110 for (; i < jpused - 1; ++i) { 111 val = jailparam_export(&jp[i + 1]); 112 113 jailparam_free(&jp[i], 1); 114 /* 115 * Given the context, the following will really only fail if 116 * they can't allocate the copy of the name or value. 117 */ 118 if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) { 119 free(val); 120 return (ENOMEM); 121 } 122 if (jailparam_import(&jp[i], val) != 0) { 123 jailparam_free(&jp[i], 1); 124 free(val); 125 return (ENOMEM); 126 } 127 free(val); 128 } 129 130 jailparam_free(&jp[i], 1); 131 --jpused; 132 return (0); 133 } 134 135 static bool 136 jailparam_addarg(char *arg) 137 { 138 char *name, *val; 139 140 if (arg == NULL) 141 return (false); 142 name = arg; 143 if ((val = strchr(arg, '=')) == NULL) { 144 fprintf(stderr, "bectl jail: malformed jail option '%s'\n", 145 arg); 146 return (false); 147 } 148 149 *val++ = '\0'; 150 if (strcmp(name, "path") == 0) { 151 if (strlen(val) >= BE_MAXPATHLEN) { 152 fprintf(stderr, 153 "bectl jail: skipping too long path assignment '%s' (max length = %d)\n", 154 val, BE_MAXPATHLEN); 155 return (false); 156 } 157 strlcpy(mnt_loc, val, sizeof(mnt_loc)); 158 } 159 jailparam_add(name, val); 160 return (true); 161 } 162 163 static int 164 jailparam_delarg(char *arg) 165 { 166 char *name, *val; 167 168 if (arg == NULL) 169 return (EINVAL); 170 name = arg; 171 if ((val = strchr(name, '=')) != NULL) 172 *val++ = '\0'; 173 174 if (strcmp(name, "path") == 0) 175 *mnt_loc = '\0'; 176 return (jailparam_del(name)); 177 } 178 179 int 180 bectl_cmd_jail(int argc, char *argv[]) 181 { 182 char *bootenv, *mountpoint; 183 int jid, mntflags, opt, ret; 184 bool default_hostname, interactive, unjail; 185 pid_t pid; 186 187 /* XXX TODO: Allow shallow */ 188 mntflags = BE_MNT_DEEP; 189 default_hostname = interactive = unjail = true; 190 jpcnt = INIT_PARAMCOUNT; 191 jp = malloc(jpcnt * sizeof(*jp)); 192 if (jp == NULL) 193 err(2, "malloc"); 194 195 jailparam_add("persist", "true"); 196 jailparam_add("allow.mount", "true"); 197 jailparam_add("allow.mount.devfs", "true"); 198 jailparam_add("enforce_statfs", "1"); 199 200 while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) { 201 switch (opt) { 202 case 'b': 203 interactive = false; 204 break; 205 case 'o': 206 if (jailparam_addarg(optarg)) { 207 /* 208 * optarg has been modified to null terminate 209 * at the assignment operator. 210 */ 211 if (strcmp(optarg, "host.hostname") == 0) 212 default_hostname = false; 213 } 214 break; 215 case 'U': 216 unjail = false; 217 break; 218 case 'u': 219 if ((ret = jailparam_delarg(optarg)) == 0) { 220 if (strcmp(optarg, "host.hostname") == 0) 221 default_hostname = true; 222 } else if (ret != ENOENT) { 223 fprintf(stderr, 224 "bectl jail: error unsetting \"%s\"\n", 225 optarg); 226 return (ret); 227 } 228 break; 229 default: 230 fprintf(stderr, "bectl jail: unknown option '-%c'\n", 231 optopt); 232 return (usage(false)); 233 } 234 } 235 236 argc -= optind; 237 argv += optind; 238 239 /* struct jail be_jail = { 0 }; */ 240 if (argc < 1) { 241 fprintf(stderr, "bectl jail: missing boot environment name\n"); 242 return (usage(false)); 243 } 244 245 bootenv = argv[0]; 246 247 /* 248 * XXX TODO: if its already mounted, perhaps there should be a flag to 249 * indicate its okay to proceed?? 250 */ 251 if (*mnt_loc == '\0') 252 mountpoint = NULL; 253 else 254 mountpoint = mnt_loc; 255 if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) { 256 fprintf(stderr, "could not mount bootenv\n"); 257 return (1); 258 } 259 260 if (default_hostname) 261 jailparam_add("host.hostname", bootenv); 262 263 /* 264 * This is our indicator that path was not set by the user, so we'll use 265 * the path that libbe generated for us. 266 */ 267 if (mountpoint == NULL) 268 jailparam_add("path", mnt_loc); 269 /* Create the jail for now, attach later as-needed */ 270 jid = jailparam_set(jp, jpused, JAIL_CREATE); 271 if (jid == -1) { 272 fprintf(stderr, "unable to create jail. error: %d\n", errno); 273 return (1); 274 } 275 276 jailparam_free(jp, jpused); 277 free(jp); 278 279 /* We're not interactive, nothing more to do here. */ 280 if (!interactive) 281 return (0); 282 283 pid = fork(); 284 switch(pid) { 285 case -1: 286 perror("fork"); 287 return (1); 288 case 0: 289 jail_attach(jid); 290 /* We're attached within the jail... good bye! */ 291 chdir("/"); 292 if (argc > 1) 293 execve(argv[1], &argv[1], NULL); 294 else 295 execl("/bin/sh", "/bin/sh", NULL); 296 fprintf(stderr, "bectl jail: failed to execute %s\n", 297 (argc > 1 ? argv[1] : "/bin/sh")); 298 _exit(1); 299 default: 300 /* Wait for the child to get back, see if we need to unjail */ 301 waitpid(pid, NULL, 0); 302 } 303 304 if (unjail) { 305 jail_remove(jid); 306 be_unmount(be, bootenv, 0); 307 } 308 309 return (0); 310 } 311 312 static int 313 bectl_search_jail_paths(const char *mnt) 314 { 315 int jid; 316 char lastjid[16]; 317 char jailpath[MAXPATHLEN]; 318 319 /* jail_getv expects name/value strings */ 320 snprintf(lastjid, sizeof(lastjid), "%d", 0); 321 322 jid = 0; 323 while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath, 324 NULL)) != -1) { 325 326 /* the jail we've been looking for */ 327 if (strcmp(jailpath, mnt) == 0) 328 return (jid); 329 330 /* update lastjid and keep on looking */ 331 snprintf(lastjid, sizeof(lastjid), "%d", jid); 332 } 333 334 return (-1); 335 } 336 337 /* 338 * Locate a jail based on an arbitrary identifier. This may be either a name, 339 * a jid, or a BE name. Returns the jid or -1 on failure. 340 */ 341 static int 342 bectl_locate_jail(const char *ident) 343 { 344 nvlist_t *belist, *props; 345 char *mnt; 346 int jid; 347 348 /* Try the easy-match first */ 349 jid = jail_getid(ident); 350 if (jid != -1) 351 return (jid); 352 353 /* Attempt to try it as a BE name, first */ 354 if (be_prop_list_alloc(&belist) != 0) 355 return (-1); 356 357 if (be_get_bootenv_props(be, belist) != 0) 358 return (-1); 359 360 if (nvlist_lookup_nvlist(belist, ident, &props) == 0) { 361 362 /* path where a boot environment is mounted */ 363 if (nvlist_lookup_string(props, "mounted", &mnt) == 0) { 364 365 /* looking for a jail that matches our bootenv path */ 366 jid = bectl_search_jail_paths(mnt); 367 be_prop_list_free(belist); 368 return (jid); 369 } 370 371 be_prop_list_free(belist); 372 } 373 374 return (-1); 375 } 376 377 int 378 bectl_cmd_unjail(int argc, char *argv[]) 379 { 380 char path[MAXPATHLEN]; 381 char *cmd, *name, *target; 382 int jid; 383 384 /* Store alias used */ 385 cmd = argv[0]; 386 387 if (argc != 2) { 388 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 389 return (usage(false)); 390 } 391 392 target = argv[1]; 393 394 /* Locate the jail */ 395 if ((jid = bectl_locate_jail(target)) == -1) { 396 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, 397 target); 398 return (1); 399 } 400 401 bzero(&path, MAXPATHLEN); 402 name = jail_getname(jid); 403 if (jail_getv(0, "name", name, "path", path, NULL) != jid) { 404 free(name); 405 fprintf(stderr, 406 "bectl %s: failed to get path for jail requested by '%s'\n", 407 cmd, target); 408 return (1); 409 } 410 411 free(name); 412 413 if (be_mounted_at(be, path, NULL) != 0) { 414 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", 415 cmd, target); 416 return (1); 417 } 418 419 jail_remove(jid); 420 be_unmount(be, target, 0); 421 422 return (0); 423 } 424