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,18 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -306,10 +310,79 @@ --depth; } +extern char **environ; + +static FILE * +open_config(const char *cfname, pid_t pid[static 1]) +{ + + int read_fd, write_fd, exec_fd, pipes[2]; + char *argv[] = { __DECONST(char *, cfname), __DECONST(char *, cfname), NULL }; + 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); + + /* Try to open the configuration for execution (to parse its standard output config instead). */ + exec_fd = openat(read_fd, "", O_EMPTY_PATH|O_EXEC); + if (exec_fd < 0) { + if (errno != EACCES) + err(1, "openat(): %s", cfname); + } else { + /* Read from the pipe instead of the configuration. */ + close(read_fd); + if (pipe(pipes)) + err(1, "pipe(): %s", cfname); + read_fd = pipes[0]; + write_fd = pipes[1]; + + /* Run the configuration as child process. */ + switch ((*pid = fork())) { + /* Failure to fork() is fatal. */ + case -1: + err(1, "fork(): %s", cfname); + break; + + /* Redirect the child's standard output into the pipe. */ + case 0: + close(read_fd); + if (write_fd != STDOUT_FILENO) { + if (dup2(write_fd, STDOUT_FILENO) != STDOUT_FILENO) + err(1, "dup2(): %s", cfname); + close(write_fd); + } + + // Replace the forked child with the configuration command. + fexecve(exec_fd, argv, environ); + err(1, "fexecve(): %s", cfname); + break; + + // Close the write end of the pipe and the executable file descriptor in the parent. + default: + 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); @@ -317,7 +390,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); @@ -325,6 +398,16 @@ 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 with status=%i): %s", status, cfname); + } } /*