xref: /freebsd/usr.sbin/jail/jail.c (revision 830940567b49bb0c08dfaed40418999e76616909)
1 /*-
2  * Copyright (c) 1999 Poul-Henning Kamp.
3  * Copyright (c) 2009 James Gritton
4  * All rights reserved.
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 AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, 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/socket.h>
34 #include <sys/sysctl.h>
35 
36 #include <arpa/inet.h>
37 #include <netinet/in.h>
38 
39 #include <ctype.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <grp.h>
43 #include <jail.h>
44 #include <login_cap.h>
45 #include <netdb.h>
46 #include <paths.h>
47 #include <pwd.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 
53 static struct jailparam *params;
54 static char **param_values;
55 static int nparams;
56 
57 static char *ip4_addr;
58 #ifdef INET6
59 static char *ip6_addr;
60 #endif
61 
62 static void add_ip_addr(char **addrp, char *newaddr);
63 #ifdef INET6
64 static void add_ip_addr46(char *newaddr);
65 #endif
66 static void add_ip_addrinfo(int ai_flags, char *value);
67 static void quoted_print(FILE *fp, char *str);
68 static void set_param(const char *name, char *value);
69 static void usage(void);
70 
71 static const char *perm_sysctl[][3] = {
72 	{ "security.jail.set_hostname_allowed",
73 	  "allow.noset_hostname", "allow.set_hostname" },
74 	{ "security.jail.sysvipc_allowed",
75 	  "allow.nosysvipc", "allow.sysvipc" },
76 	{ "security.jail.allow_raw_sockets",
77 	  "allow.noraw_sockets", "allow.raw_sockets" },
78 	{ "security.jail.chflags_allowed",
79 	  "allow.nochflags", "allow.chflags" },
80 	{ "security.jail.mount_allowed",
81 	  "allow.nomount", "allow.mount" },
82 	{ "security.jail.socket_unixiproute_only",
83 	  "allow.socket_af", "allow.nosocket_af" },
84 };
85 
86 extern char **environ;
87 
88 #define GET_USER_INFO do {						\
89 	pwd = getpwnam(username);					\
90 	if (pwd == NULL) {						\
91 		if (errno)						\
92 			err(1, "getpwnam: %s", username);		\
93 		else							\
94 			errx(1, "%s: no such user", username);		\
95 	}								\
96 	lcap = login_getpwclass(pwd);					\
97 	if (lcap == NULL)						\
98 		err(1, "getpwclass: %s", username);			\
99 	ngroups = ngroups_max;						\
100 	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
101 		err(1, "getgrouplist: %s", username);			\
102 } while (0)
103 
104 int
105 main(int argc, char **argv)
106 {
107 	login_cap_t *lcap = NULL;
108 	struct passwd *pwd = NULL;
109 	gid_t *groups;
110 	size_t sysvallen;
111 	int ch, cmdarg, i, jail_set_flags, jid, ngroups, sysval;
112 	int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag;
113 	long ngroups_max;
114 	unsigned pi;
115 	char *jailname, *securelevel, *username, *JidFile;
116 	char enforce_statfs[4];
117 	static char *cleanenv;
118 	const char *shell, *p = NULL;
119 	FILE *fp;
120 
121 	hflag = iflag = Jflag = lflag = rflag = uflag = Uflag =
122 	    jail_set_flags = 0;
123 	cmdarg = jid = -1;
124 	jailname = securelevel = username = JidFile = cleanenv = NULL;
125 	fp = NULL;
126 
127 	ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
128 	if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
129 		err(1, "malloc");
130 
131 	while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) {
132 		switch (ch) {
133 		case 'd':
134 			jail_set_flags |= JAIL_DYING;
135 			break;
136 		case 'h':
137 			hflag = 1;
138 			break;
139 		case 'i':
140 			iflag = 1;
141 			break;
142 		case 'J':
143 			JidFile = optarg;
144 			Jflag = 1;
145 			break;
146 		case 'n':
147 			jailname = optarg;
148 			break;
149 		case 's':
150 			securelevel = optarg;
151 			break;
152 		case 'u':
153 			username = optarg;
154 			uflag = 1;
155 			break;
156 		case 'U':
157 			username = optarg;
158 			Uflag = 1;
159 			break;
160 		case 'l':
161 			lflag = 1;
162 			break;
163 		case 'c':
164 			jail_set_flags |= JAIL_CREATE;
165 			break;
166 		case 'm':
167 			jail_set_flags |= JAIL_UPDATE;
168 			break;
169 		case 'r':
170 			jid = jail_getid(optarg);
171 			if (jid < 0)
172 				errx(1, "%s", jail_errmsg);
173 			rflag = 1;
174 			break;
175 		default:
176 			usage();
177 		}
178 	}
179 	argc -= optind;
180 	argv += optind;
181 	if (rflag) {
182 		if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag)
183 			usage();
184 		if (jail_remove(jid) < 0)
185 			err(1, "jail_remove");
186 		exit (0);
187 	}
188 	if (argc == 0)
189 		usage();
190 	if (uflag && Uflag)
191 		usage();
192 	if (lflag && username == NULL)
193 		usage();
194 	if (uflag)
195 		GET_USER_INFO;
196 
197 	if (jailname)
198 		set_param("name", jailname);
199 	if (securelevel)
200 		set_param("securelevel", securelevel);
201 	if (jail_set_flags) {
202 		for (i = 0; i < argc; i++) {
203 			if (!strncmp(argv[i], "command=", 8)) {
204 				cmdarg = i;
205 				argv[cmdarg] += 8;
206 				jail_set_flags |= JAIL_ATTACH;
207 				break;
208 			}
209 			if (hflag) {
210 				if (!strncmp(argv[i], "ip4.addr=", 9)) {
211 					add_ip_addr(&ip4_addr, argv[i] + 9);
212 					break;
213 				}
214 #ifdef INET6
215 				if (!strncmp(argv[i], "ip6.addr=", 9)) {
216 					add_ip_addr(&ip6_addr, argv[i] + 9);
217 					break;
218 				}
219 #endif
220 				if (!strncmp(argv[i], "host.hostname=", 14))
221 					add_ip_addrinfo(0, argv[i] + 14);
222 			}
223 			set_param(NULL, argv[i]);
224 		}
225 	} else {
226 		if (argc < 4 || argv[0][0] != '/')
227 			errx(1, "%s\n%s",
228 			   "no -c or -m, so this must be an old-style command.",
229 			   "But it doesn't look like one.");
230 		set_param("path", argv[0]);
231 		set_param("host.hostname", argv[1]);
232 		if (hflag)
233 			add_ip_addrinfo(0, argv[1]);
234 		if (argv[2][0] != '\0')
235 #ifdef INET6
236 			add_ip_addr46(argv[2]);
237 #else
238 			add_ip_addr(&ip4_addr, argv[2]);
239 #endif
240 		cmdarg = 3;
241 		/* Emulate the defaults from security.jail.* sysctls */
242 		sysvallen = sizeof(sysval);
243 		if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen,
244 		    NULL, 0) == 0 && sysval == 0) {
245 			for (pi = 0; pi < sizeof(perm_sysctl) /
246 			     sizeof(perm_sysctl[0]); pi++) {
247 				sysvallen = sizeof(sysval);
248 				if (sysctlbyname(perm_sysctl[pi][0],
249 				    &sysval, &sysvallen, NULL, 0) == 0)
250 					set_param(perm_sysctl[pi]
251 					    [sysval ? 2 : 1], NULL);
252 			}
253 			sysvallen = sizeof(sysval);
254 			if (sysctlbyname("security.jail.enforce_statfs",
255 			    &sysval, &sysvallen, NULL, 0) == 0) {
256 				snprintf(enforce_statfs,
257 				    sizeof(enforce_statfs), "%d", sysval);
258 				set_param("enforce_statfs", enforce_statfs);
259 			}
260 		}
261 	}
262 	if (ip4_addr != NULL)
263 		set_param("ip4.addr", ip4_addr);
264 #ifdef INET6
265 	if (ip6_addr != NULL)
266 		set_param("ip6.addr", ip6_addr);
267 #endif
268 
269 	if (Jflag) {
270 		fp = fopen(JidFile, "w");
271 		if (fp == NULL)
272 			errx(1, "Could not create JidFile: %s", JidFile);
273 	}
274 	jid = jailparam_set(params, nparams,
275 	    jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH);
276 	if (jid < 0)
277 		errx(1, "%s", jail_errmsg);
278 	if (iflag) {
279 		printf("%d\n", jid);
280 		fflush(stdout);
281 	}
282 	if (Jflag) {
283 		if (jail_set_flags) {
284 			fprintf(fp, "jid=%d", jid);
285 			for (i = 0; i < nparams; i++)
286 				if (strcmp(params[i].jp_name, "jid")) {
287 					fprintf(fp, " %s",
288 					    (char *)params[i].jp_name);
289 					if (param_values[i]) {
290 						putc('=', fp);
291 						quoted_print(fp,
292 						    param_values[i]);
293 					}
294 				}
295 			fprintf(fp, "\n");
296 		} else {
297 			for (i = 0; i < nparams; i++)
298 				if (!strcmp(params[i].jp_name, "path"))
299 					break;
300 #ifdef INET6
301 			fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n",
302 			    jid, i < nparams
303 			    ? (char *)params[i].jp_value : argv[0],
304 			    argv[1], ip4_addr ? ip4_addr : "",
305 			    ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0]
306 			    ? "," : "", ip6_addr ? ip6_addr : "", argv[3]);
307 #else
308 			fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
309 			    jid, i < nparams
310 			    ? (char *)params[i].jp_value : argv[0],
311 			    argv[1], ip4_addr ? ip4_addr : "", argv[3]);
312 #endif
313 		}
314 		(void)fclose(fp);
315 	}
316 	if (cmdarg < 0)
317 		exit(0);
318 	if (username != NULL) {
319 		if (Uflag)
320 			GET_USER_INFO;
321 		if (lflag) {
322 			p = getenv("TERM");
323 			environ = &cleanenv;
324 		}
325 		if (setgroups(ngroups, groups) != 0)
326 			err(1, "setgroups");
327 		if (setgid(pwd->pw_gid) != 0)
328 			err(1, "setgid");
329 		if (setusercontext(lcap, pwd, pwd->pw_uid,
330 		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
331 			err(1, "setusercontext");
332 		login_close(lcap);
333 	}
334 	if (lflag) {
335 		if (*pwd->pw_shell)
336 			shell = pwd->pw_shell;
337 		else
338 			shell = _PATH_BSHELL;
339 		if (chdir(pwd->pw_dir) < 0)
340 			errx(1, "no home directory");
341 		setenv("HOME", pwd->pw_dir, 1);
342 		setenv("SHELL", shell, 1);
343 		setenv("USER", pwd->pw_name, 1);
344 		if (p)
345 			setenv("TERM", p, 1);
346 	}
347 	execvp(argv[cmdarg], argv + cmdarg);
348 	err(1, "execvp: %s", argv[cmdarg]);
349 }
350 
351 static void
352 add_ip_addr(char **addrp, char *value)
353 {
354 	int addrlen;
355 	char *addr;
356 
357 	if (!*addrp) {
358 		*addrp = strdup(value);
359 		if (!*addrp)
360 			err(1, "malloc");
361 	} else if (value[0]) {
362 		addrlen = strlen(*addrp) + strlen(value) + 2;
363 		addr = malloc(addrlen);
364 		if (!addr)
365 			err(1, "malloc");
366 		snprintf(addr, addrlen, "%s,%s", *addrp, value);
367 		free(*addrp);
368 		*addrp = addr;
369 	}
370 }
371 
372 #ifdef INET6
373 static void
374 add_ip_addr46(char *value)
375 {
376 	char *p, *np;
377 
378 	for (p = value;; p = np + 1)
379 	{
380 		np = strchr(p, ',');
381 		if (np)
382 			*np = '\0';
383 		add_ip_addrinfo(AI_NUMERICHOST, p);
384 		if (!np)
385 			break;
386 	}
387 }
388 #endif
389 
390 static void
391 add_ip_addrinfo(int ai_flags, char *value)
392 {
393 	struct addrinfo hints, *ai0, *ai;
394 	struct in_addr addr4;
395 	size_t size;
396 	int error, ip4ok;
397 	int mib[4];
398 	char avalue4[INET_ADDRSTRLEN];
399 #ifdef INET6
400 	struct in6_addr addr6;
401 	int ip6ok;
402 	char avalue6[INET6_ADDRSTRLEN];
403 #endif
404 
405 	/* Look up the hostname (or get the address) */
406 	memset(&hints, 0, sizeof(hints));
407 	hints.ai_socktype = SOCK_STREAM;
408 #ifdef INET6
409 	hints.ai_family = PF_UNSPEC;
410 #else
411 	hints.ai_family = PF_INET;
412 #endif
413 	hints.ai_flags = ai_flags;
414 	error = getaddrinfo(value, NULL, &hints, &ai0);
415 	if (error != 0)
416 		errx(1, "hostname %s: %s", value, gai_strerror(error));
417 
418 	/*
419 	 * Silently ignore unsupported address families from DNS lookups.
420 	 * But if this is a numeric address, let the kernel give the error.
421 	 */
422 	if (ai_flags & AI_NUMERICHOST)
423 		ip4ok =
424 #ifdef INET6
425 		    ip6ok =
426 #endif
427 		    1;
428 	else {
429 		size = 4;
430 		ip4ok = (sysctlnametomib("security.jail.param.ip4", mib,
431 		    &size) == 0);
432 #ifdef INET6
433 		size = 4;
434 		ip6ok = (sysctlnametomib("security.jail.param.ip6", mib,
435 		    &size) == 0);
436 #endif
437 	}
438 
439 	/* Convert the addresses to ASCII so set_param can convert them back. */
440 	for (ai = ai0; ai; ai = ai->ai_next)
441 		switch (ai->ai_family) {
442 		case AF_INET:
443 			if (!ip4ok)
444 				break;
445 			memcpy(&addr4, &((struct sockaddr_in *)
446 			    (void *)ai->ai_addr)->sin_addr, sizeof(addr4));
447 			if (inet_ntop(AF_INET, &addr4, avalue4,
448 			    INET_ADDRSTRLEN) == NULL)
449 				err(1, "inet_ntop");
450 			add_ip_addr(&ip4_addr, avalue4);
451 			break;
452 #ifdef INET6
453 		case AF_INET6:
454 			if (!ip6ok)
455 				break;
456 			memcpy(&addr6, &((struct sockaddr_in6 *)
457 			    (void *)ai->ai_addr)->sin6_addr, sizeof(addr6));
458 			if (inet_ntop(AF_INET6, &addr6, avalue6,
459 			    INET6_ADDRSTRLEN) == NULL)
460 				err(1, "inet_ntop");
461 			add_ip_addr(&ip6_addr, avalue6);
462 			break;
463 #endif
464 		}
465 	freeaddrinfo(ai0);
466 }
467 
468 static void
469 quoted_print(FILE *fp, char *str)
470 {
471 	int c, qc;
472 	char *p = str;
473 
474 	/* An empty string needs quoting. */
475 	if (!*p) {
476 		fputs("\"\"", fp);
477 		return;
478 	}
479 
480 	/*
481 	 * The value will be surrounded by quotes if it contains spaces
482 	 * or quotes.
483 	 */
484 	qc = strchr(p, '\'') ? '"'
485 	    : strchr(p, '"') ? '\''
486 	    : strchr(p, ' ') || strchr(p, '\t') ? '"'
487 	    : 0;
488 	if (qc)
489 		putc(qc, fp);
490 	while ((c = *p++)) {
491 		if (c == '\\' || c == qc)
492 			putc('\\', fp);
493 		putc(c, fp);
494 	}
495 	if (qc)
496 		putc(qc, fp);
497 }
498 
499 static void
500 set_param(const char *name, char *value)
501 {
502 	struct jailparam *param;
503 	int i;
504 
505 	static int paramlistsize;
506 
507 	/* Separate the name from the value, if not done already. */
508 	if (name == NULL) {
509 		name = value;
510 		if ((value = strchr(value, '=')))
511 			*value++ = '\0';
512 	}
513 
514 	/* Check for repeat parameters */
515 	for (i = 0; i < nparams; i++)
516 		if (!strcmp(name, params[i].jp_name)) {
517 			jailparam_free(params + i, 1);
518 			memcpy(params + i, params + i + 1,
519 			    (--nparams - i) * sizeof(struct jailparam));
520 			break;
521 		}
522 
523 	/* Make sure there is room for the new param record. */
524 	if (!nparams) {
525 		paramlistsize = 32;
526 		params = malloc(paramlistsize * sizeof(*params));
527 		param_values = malloc(paramlistsize * sizeof(*param_values));
528 		if (params == NULL || param_values == NULL)
529 			err(1, "malloc");
530 	} else if (nparams >= paramlistsize) {
531 		paramlistsize *= 2;
532 		params = realloc(params, paramlistsize * sizeof(*params));
533 		param_values = realloc(param_values,
534 		    paramlistsize * sizeof(*param_values));
535 		if (params == NULL)
536 			err(1, "realloc");
537 	}
538 
539 	/* Look up the paramter. */
540 	param_values[nparams] = value;
541 	param = params + nparams++;
542 	if (jailparam_init(param, name) < 0 ||
543 	    jailparam_import(param, value) < 0)
544 		errx(1, "%s", jail_errmsg);
545 }
546 
547 static void
548 usage(void)
549 {
550 
551 	(void)fprintf(stderr,
552 	    "usage: jail [-d] [-h] [-i] [-J jid_file] "
553 			"[-l -u username | -U username]\n"
554 	    "            [-c | -m] param=value ... [command=command ...]\n"
555 	    "       jail [-r jail]\n");
556 	exit(1);
557 }
558