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,77 @@ --depth; } +extern char **environ; + +static FILE * +open_config(const char *cfname, pid_t pid[static 1]) +{ + + int read_fd = open(cfname, O_RDONLY), write_fd, exec_fd, pipes[2]; + char *argv[] = { (char*)(uintptr_t)cfname, (char *)(uintptr_t)cfname, NULL }; // DECONST *sigh* + + // Invalidate the child PID. + *pid = -1; + + // The configuration must be readable. + 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 *file = fdopen(read_fd, "r"); + if (!file) + 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; void *scanner; yylex_init_extra(&cflex, &scanner); @@ -317,7 +388,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 +396,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); + } } /*