Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F157252652
D46284.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
D46284.diff
View Options
diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c
--- a/usr.sbin/jail/config.c
+++ b/usr.sbin/jail/config.c
@@ -29,14 +29,20 @@
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/sysctl.h>
+#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <err.h>
+#include <fcntl.h>
#include <glob.h>
+#include <libgen.h>
#include <netdb.h>
+#include <pwd.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -82,6 +88,7 @@
PF_INTERNAL | PF_BOOL},
[IP_EXEC_SYSTEM_USER] = {"exec.system_user", PF_INTERNAL},
[IP_EXEC_TIMEOUT] = {"exec.timeout", PF_INTERNAL | PF_INT},
+ [IP_ALLOW_EXEC_INCLUDE] = {"allow.exec_include", PF_INTERNAL | PF_BOOL},
#if defined(INET) || defined(INET6)
[IP_INTERFACE] = {"interface", PF_INTERNAL},
[IP_IP_HOSTNAME] = {"ip_hostname", PF_INTERNAL | PF_BOOL},
@@ -317,10 +324,184 @@
--depth;
}
+extern char **environ;
+
+/*
+ * Check if a file descriptor references a file is executable to the process
+ * at the time of check.
+ */
+static bool
+is_executable(const int fd)
+{
+ return faccessat(fd, "", X_OK, AT_EMPTY_PATH) == 0;
+}
+
+/*
+ * Check if dynamic includes are enabled.
+ *
+ * The check searches for the boolean internal parameter "dynamic_include"
+ * in the current jail, up its ancestor chain, and in the global jail
+ * in that order.
+ *
+ * An explicit search is needed because the parameter inheritance
+ * gets applied too late for use in open_config().
+ */
+static bool
+dynamic_include(void)
+{
+ struct cfjail *jail;
+ struct cfparam *current, *found = NULL;
+ const char *const name = intparams[IP_ALLOW_EXEC_INCLUDE].name;
+
+ // Search the jail and its ancestors.
+ jail = current_jail;
+ while (jail != NULL && found == NULL) {
+ TAILQ_FOREACH(current, &jail->params, tq)
+ if (equalopts(current->name, name))
+ found = current;
+ jail = jail->cfparent;
+ }
+
+ // Search the global jail.
+ if (global_jail != NULL && found == NULL)
+ TAILQ_FOREACH(current, &global_jail->params, tq)
+ if (equalopts(current->name, name))
+ found = current;
+
+ // Default to disabled.
+ return found != NULL && !!bool_param(found);
+}
+
+/*
+ * Open a jail configuration file.
+ *
+ * The configuration file can be dynamic (aka executable).
+ * For a dynamic configuration the FILE wraps the pipe to
+ * the child process.
+ * The PID is an out parameter and allowing the caller
+ * to wait for the exit status of the child.
+ * The PID is set to -1 if no child has been created.
+ */
+static FILE *
+open_config(const char *cfname, pid_t *const pid)
+{
+
+ int read_fd;
+ FILE *file;
+
+ // Invalidate the child PID.
+ *pid = -1;
+
+ // The configuration must be readable.
+ read_fd = open(cfname, O_RDONLY);
+ if (read_fd < 0)
+ err(1, "open(): %s", cfname);
+
+ const bool is_exec = is_executable(read_fd);
+ const bool dyn_include = dynamic_include();
+
+ // If the configuration is executable run it as a child of jail(8)
+ // and parse its standard output as jail.conf(5) configuration.
+ if (is_exec && !dyn_include) {
+ warnx("Warning: \"%s\" is executable, but executable includes "
+ "are disabled.", cfname);
+ } else if (is_exec && dyn_include) {
+ int exec_fd, write_fd;
+ const size_t cfname_size = strlen(cfname) + sizeof("");
+ char dir_buf[PATH_MAX], base_buf[PATH_MAX];
+ if (cfname_size > PATH_MAX) {
+ errno = ENAMETOOLONG;
+ err(1, "open_config(): %s", cfname);
+ }
+
+ // Set the argument list:
+ // <jail_conf> <jail_conf> <jail_dir> <jail_base> <jail_name>
+ char *const jail_conf = __DECONST(char *, cfname);
+ char *const jail_dir =
+ dirname(memcpy(dir_buf, cfname, cfname_size));
+ char *const jail_base =
+ basename(memcpy(base_buf, cfname, cfname_size));
+ char *const jail_name = current_jail && current_jail->name
+ ? current_jail->name : __DECONST(char *, "");
+ char *const argv[] = {
+ jail_conf,
+ jail_conf, jail_dir, jail_base, jail_name,
+ NULL
+ };
+
+ // The read end of the pipe replaces the configuration file
+ // as input to the parser, but the file descriptor
+ // is still needed as executable.
+ {
+ int pipes[2];
+ if (pipe(pipes))
+ err(1, "pipe(): %s", cfname);
+ exec_fd = read_fd;
+ read_fd = pipes[0];
+ write_fd = pipes[1];
+ }
+
+ // Run the configuration as child process.
+ switch ((*pid = fork())) {
+
+ // Failed to fork().
+ case -1:
+ err(1, "fork(): %s", cfname);
+ break;
+
+ // After successful fork() inside the child process.
+ case 0:
+ // Export the arguments as env vars too.
+ if (setenv("JAIL_CONF", cfname, 1))
+ err(1, "setenv(\"JAIL_CONF\", \"%s\"): %s",
+ cfname, cfname);
+ if (setenv("JAIL_DIR", jail_dir, 1))
+ err(1, "setenv(\"JAIL_DIR\", \"%s\"): %s",
+ jail_dir, cfname);
+ if (setenv("JAIL_BASE", jail_base, 1))
+ err(1, "setenv(\"JAIL_BASE\", \"%s\"): %s",
+ jail_base, cfname);
+ if (setenv("JAIL_NAME", jail_name, 1))
+ err(1, "setenv(\"JAIL_NAME\", \"%s\"): %s",
+ jail_name, cfname);
+
+ // Redirect the child's standard output into the pipe.
+ if (write_fd != STDOUT_FILENO) {
+ if (dup2(write_fd, STDOUT_FILENO) < 0)
+ err(1, "dup2(): %s", cfname);
+ close(write_fd);
+ }
+
+ // Replace the forked child with the
+ // dynamic configuration command.
+ close(read_fd);
+ fexecve(exec_fd, argv, environ);
+ err(1, "fexecve(): %s", cfname);
+ break;
+
+ // After successful fork() inside the parent process.
+ default:
+ // Close the write end of the pipe as well as
+ // the executable file descriptor in the parent.
+ close(write_fd);
+ close(exec_fd);
+ break;
+ }
+ }
+
+ // Wrap a FILE handle around the read-only file descriptor.
+ file = fdopen(read_fd, "r");
+ if (file == NULL)
+ err(1, "fdopen(): %s", cfname);
+
+ return file;
+}
+
static void
parse_config(const char *cfname, int is_stdin)
{
struct cflex cflex = {.cfname = cfname, .error = 0};
+ pid_t child = -1;
void *scanner;
yylex_init_extra(&cflex, &scanner);
@@ -328,7 +509,7 @@
cflex.cfname = "STDIN";
yyset_in(stdin, scanner);
} else {
- FILE *yfp = fopen(cfname, "r");
+ FILE *yfp = open_config(cfname, &child);
if (!yfp)
err(1, "%s", cfname);
yyset_in(yfp, scanner);
@@ -336,6 +517,17 @@
if (yyparse(scanner) || cflex.error)
exit(1);
yylex_destroy(scanner);
+ if (child > 0) {
+ pid_t exited;
+ int status;
+ do {
+ exited = waitpid(child, &status, 0);
+ } while (exited < 0 || errno == EINTR);
+ status = WEXITSTATUS(status);
+ if (status != 0)
+ errx(status, "Config child failed (exit = %i): %s",
+ status, cfname);
+ }
}
/*
diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h
--- a/usr.sbin/jail/jailp.h
+++ b/usr.sbin/jail/jailp.h
@@ -35,6 +35,9 @@
#include <jail.h>
#include <stdio.h>
+extern struct cfjail *current_jail;
+extern struct cfjail *global_jail;
+
#define CONF_FILE "/etc/jail.conf"
#define DEP_FROM 0
@@ -97,6 +100,7 @@
IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */
IP_EXEC_SYSTEM_USER, /* Run non-jailed commands as this user */
IP_EXEC_TIMEOUT, /* Time to wait for a command to complete */
+ IP_ALLOW_EXEC_INCLUDE, /* Allow dynamic jail.conf to be executed */
#if defined(INET) || defined(INET6)
IP_INTERFACE, /* Add IP addresses to this interface */
IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */
diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y
--- a/usr.sbin/jail/jailparse.y
+++ b/usr.sbin/jail/jailparse.y
@@ -38,8 +38,8 @@
#define YYDEBUG 1
#endif
-static struct cfjail *current_jail;
-static struct cfjail *global_jail;
+struct cfjail *current_jail = NULL;
+struct cfjail *global_jail = NULL;
%}
%union {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, May 20, 5:52 PM (14 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
33352982
Default Alt Text
D46284.diff (7 KB)
Attached To
Mode
D46284: Add the ability have executable jail.conf
Attached
Detach File
Event Timeline
Log In to Comment