jail: avoid leaking jail config fds to exec.* hooks

The jail(8) command must not leave parsed configuration files open
since the file descriptors will be leaked to child processes
including the untrusted exec.start or exec.stop hooks.

While fopen() doesn't provide direct access to O_CLOEXEC, it does
provide access to FD_CLOEXEC via "e" in the mode string which
provides the desired defense in depth against leaking file descriptors
into exec.* hooks since those always execve() into a shell.

Jail configuration is potentially sensitive and some hooks execute from
within the jail context, leaving some opening for the jail to exfiltrate
information about the host environment.

(Commit message wordsmithed by kevans)

PR:		295052
Reviewed by:	kevans
MFC after:	3 days
This commit is contained in:
Jan Bramkamp
2026-05-06 18:28:53 -05:00
committed by Kyle Evans
parent 3348fa7a45
commit 276d9b88a9
+4 -1
View File
@@ -321,6 +321,7 @@ static void
parse_config(const char *cfname, int is_stdin) parse_config(const char *cfname, int is_stdin)
{ {
struct cflex cflex = {.cfname = cfname, .error = 0}; struct cflex cflex = {.cfname = cfname, .error = 0};
FILE *yfp = NULL;
void *scanner; void *scanner;
yylex_init_extra(&cflex, &scanner); yylex_init_extra(&cflex, &scanner);
@@ -328,7 +329,7 @@ parse_config(const char *cfname, int is_stdin)
cflex.cfname = "STDIN"; cflex.cfname = "STDIN";
yyset_in(stdin, scanner); yyset_in(stdin, scanner);
} else { } else {
FILE *yfp = fopen(cfname, "r"); yfp = fopen(cfname, "re");
if (!yfp) if (!yfp)
err(1, "%s", cfname); err(1, "%s", cfname);
yyset_in(yfp, scanner); yyset_in(yfp, scanner);
@@ -336,6 +337,8 @@ parse_config(const char *cfname, int is_stdin)
if (yyparse(scanner) || cflex.error) if (yyparse(scanner) || cflex.error)
exit(1); exit(1);
yylex_destroy(scanner); yylex_destroy(scanner);
if (yfp != NULL)
fclose(yfp);
} }
/* /*