exec: Generic execfd support

Most of the support for passing the file descriptor of an executable
to an interpreter already lives in the generic code and in binfmt_elf.
Rework the fields in binfmt_elf that deal with executable file
descriptor passing to make executable file descriptor passing a first
class concept.

Move the fd_install from binfmt_misc into begin_new_exec after the new
creds have been installed.  This means that accessing the file through
/proc/<pid>/fd/N is able to see the creds for the new executable
before allowing access to the new executables files.

Performing the install of the executables file descriptor after
the point of no return also means that nothing special needs to
be done on error.  The exiting of the process will close all
of it's open files.

Move the would_dump from binfmt_misc into begin_new_exec right
after would_dump is called on the bprm->file.  This makes it
obvious this case exists and that no nesting of bprm->file is
currently supported.

In binfmt_misc the movement of fd_install into generic code means
that it's special error exit path is no longer needed.

Link: https://lkml.kernel.org/r/87y2poyd91.fsf_-_@x220.int.ebiederm.org
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
This commit is contained in:
Eric W. Biederman 2020-05-14 15:17:40 -05:00
parent ccbb18b673
commit b8a61c9e7b
5 changed files with 32 additions and 41 deletions

View File

@ -273,8 +273,8 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
NEW_AUX_ENT(AT_BASE_PLATFORM, NEW_AUX_ENT(AT_BASE_PLATFORM,
(elf_addr_t)(unsigned long)u_base_platform); (elf_addr_t)(unsigned long)u_base_platform);
} }
if (bprm->interp_flags & BINPRM_FLAGS_EXECFD) { if (bprm->have_execfd) {
NEW_AUX_ENT(AT_EXECFD, bprm->interp_data); NEW_AUX_ENT(AT_EXECFD, bprm->execfd);
} }
#undef NEW_AUX_ENT #undef NEW_AUX_ENT
/* AT_NULL is zero; clear the rest too */ /* AT_NULL is zero; clear the rest too */

View File

@ -628,10 +628,10 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm,
(elf_addr_t) (unsigned long) u_base_platform); (elf_addr_t) (unsigned long) u_base_platform);
} }
if (bprm->interp_flags & BINPRM_FLAGS_EXECFD) { if (bprm->have_execfd) {
nr = 0; nr = 0;
csp -= 2 * sizeof(unsigned long); csp -= 2 * sizeof(unsigned long);
NEW_AUX_ENT(AT_EXECFD, bprm->interp_data); NEW_AUX_ENT(AT_EXECFD, bprm->execfd);
} }
nr = 0; nr = 0;

View File

@ -134,7 +134,6 @@ static int load_misc_binary(struct linux_binprm *bprm)
Node *fmt; Node *fmt;
struct file *interp_file = NULL; struct file *interp_file = NULL;
int retval; int retval;
int fd_binary = -1;
retval = -ENOEXEC; retval = -ENOEXEC;
if (!enabled) if (!enabled)
@ -161,29 +160,12 @@ static int load_misc_binary(struct linux_binprm *bprm)
} }
if (fmt->flags & MISC_FMT_OPEN_BINARY) { if (fmt->flags & MISC_FMT_OPEN_BINARY) {
/* Pass the open binary to the interpreter */
/* if the binary should be opened on behalf of the bprm->have_execfd = 1;
* interpreter than keep it open and assign descriptor bprm->executable = bprm->file;
* to it
*/
fd_binary = get_unused_fd_flags(0);
if (fd_binary < 0) {
retval = fd_binary;
goto ret;
}
fd_install(fd_binary, bprm->file);
/* if the binary is not readable than enforce mm->dumpable=0
regardless of the interpreter's permissions */
would_dump(bprm, bprm->file);
allow_write_access(bprm->file); allow_write_access(bprm->file);
bprm->file = NULL; bprm->file = NULL;
/* mark the bprm that fd should be passed to interp */
bprm->interp_flags |= BINPRM_FLAGS_EXECFD;
bprm->interp_data = fd_binary;
} else { } else {
allow_write_access(bprm->file); allow_write_access(bprm->file);
fput(bprm->file); fput(bprm->file);
@ -192,19 +174,19 @@ static int load_misc_binary(struct linux_binprm *bprm)
/* make argv[1] be the path to the binary */ /* make argv[1] be the path to the binary */
retval = copy_strings_kernel(1, &bprm->interp, bprm); retval = copy_strings_kernel(1, &bprm->interp, bprm);
if (retval < 0) if (retval < 0)
goto error; goto ret;
bprm->argc++; bprm->argc++;
/* add the interp as argv[0] */ /* add the interp as argv[0] */
retval = copy_strings_kernel(1, &fmt->interpreter, bprm); retval = copy_strings_kernel(1, &fmt->interpreter, bprm);
if (retval < 0) if (retval < 0)
goto error; goto ret;
bprm->argc++; bprm->argc++;
/* Update interp in case binfmt_script needs it. */ /* Update interp in case binfmt_script needs it. */
retval = bprm_change_interp(fmt->interpreter, bprm); retval = bprm_change_interp(fmt->interpreter, bprm);
if (retval < 0) if (retval < 0)
goto error; goto ret;
if (fmt->flags & MISC_FMT_OPEN_FILE) { if (fmt->flags & MISC_FMT_OPEN_FILE) {
interp_file = file_clone_open(fmt->interp_file); interp_file = file_clone_open(fmt->interp_file);
@ -215,7 +197,7 @@ static int load_misc_binary(struct linux_binprm *bprm)
} }
retval = PTR_ERR(interp_file); retval = PTR_ERR(interp_file);
if (IS_ERR(interp_file)) if (IS_ERR(interp_file))
goto error; goto ret;
bprm->file = interp_file; bprm->file = interp_file;
if (fmt->flags & MISC_FMT_CREDENTIALS) if (fmt->flags & MISC_FMT_CREDENTIALS)
@ -223,17 +205,11 @@ static int load_misc_binary(struct linux_binprm *bprm)
retval = search_binary_handler(bprm); retval = search_binary_handler(bprm);
if (retval < 0) if (retval < 0)
goto error; goto ret;
ret: ret:
dput(fmt->dentry); dput(fmt->dentry);
return retval; return retval;
error:
if (fd_binary > 0)
ksys_close(fd_binary);
bprm->interp_flags = 0;
bprm->interp_data = 0;
goto ret;
} }
/* Command parsers */ /* Command parsers */

View File

@ -1323,7 +1323,10 @@ int begin_new_exec(struct linux_binprm * bprm)
*/ */
set_mm_exe_file(bprm->mm, bprm->file); set_mm_exe_file(bprm->mm, bprm->file);
/* If the binary is not readable then enforce mm->dumpable=0 */
would_dump(bprm, bprm->file); would_dump(bprm, bprm->file);
if (bprm->have_execfd)
would_dump(bprm, bprm->executable);
/* /*
* Release all of the old mmap stuff * Release all of the old mmap stuff
@ -1427,6 +1430,16 @@ int begin_new_exec(struct linux_binprm * bprm)
* credentials; any time after this it may be unlocked. * credentials; any time after this it may be unlocked.
*/ */
security_bprm_committed_creds(bprm); security_bprm_committed_creds(bprm);
/* Pass the opened binary to the interpreter. */
if (bprm->have_execfd) {
retval = get_unused_fd_flags(0);
if (retval < 0)
goto out_unlock;
fd_install(retval, bprm->executable);
bprm->executable = NULL;
bprm->execfd = retval;
}
return 0; return 0;
out_unlock: out_unlock:
@ -1516,6 +1529,8 @@ static void free_bprm(struct linux_binprm *bprm)
allow_write_access(bprm->file); allow_write_access(bprm->file);
fput(bprm->file); fput(bprm->file);
} }
if (bprm->executable)
fput(bprm->executable);
/* If a binfmt changed the interp, free it. */ /* If a binfmt changed the interp, free it. */
if (bprm->interp != bprm->filename) if (bprm->interp != bprm->filename)
kfree(bprm->interp); kfree(bprm->interp);

View File

@ -26,6 +26,9 @@ struct linux_binprm {
unsigned long p; /* current top of mem */ unsigned long p; /* current top of mem */
unsigned long argmin; /* rlimit marker for copy_strings() */ unsigned long argmin; /* rlimit marker for copy_strings() */
unsigned int unsigned int
/* Should an execfd be passed to userspace? */
have_execfd:1,
/* It is safe to use the creds of a script (see binfmt_misc) */ /* It is safe to use the creds of a script (see binfmt_misc) */
preserve_creds:1, preserve_creds:1,
/* /*
@ -48,6 +51,7 @@ struct linux_binprm {
unsigned int taso:1; unsigned int taso:1;
#endif #endif
unsigned int recursion_depth; /* only for search_binary_handler() */ unsigned int recursion_depth; /* only for search_binary_handler() */
struct file * executable; /* Executable to pass to the interpreter */
struct file * file; struct file * file;
struct cred *cred; /* new credentials */ struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
@ -58,7 +62,7 @@ struct linux_binprm {
of the time same as filename, but could be of the time same as filename, but could be
different for binfmt_{misc,script} */ different for binfmt_{misc,script} */
unsigned interp_flags; unsigned interp_flags;
unsigned interp_data; int execfd; /* File descriptor of the executable */
unsigned long loader, exec; unsigned long loader, exec;
struct rlimit rlim_stack; /* Saved RLIMIT_STACK used during exec. */ struct rlimit rlim_stack; /* Saved RLIMIT_STACK used during exec. */
@ -69,10 +73,6 @@ struct linux_binprm {
#define BINPRM_FLAGS_ENFORCE_NONDUMP_BIT 0 #define BINPRM_FLAGS_ENFORCE_NONDUMP_BIT 0
#define BINPRM_FLAGS_ENFORCE_NONDUMP (1 << BINPRM_FLAGS_ENFORCE_NONDUMP_BIT) #define BINPRM_FLAGS_ENFORCE_NONDUMP (1 << BINPRM_FLAGS_ENFORCE_NONDUMP_BIT)
/* fd of the binary should be passed to the interpreter */
#define BINPRM_FLAGS_EXECFD_BIT 1
#define BINPRM_FLAGS_EXECFD (1 << BINPRM_FLAGS_EXECFD_BIT)
/* filename of the binary will be inaccessible after exec */ /* filename of the binary will be inaccessible after exec */
#define BINPRM_FLAGS_PATH_INACCESSIBLE_BIT 2 #define BINPRM_FLAGS_PATH_INACCESSIBLE_BIT 2
#define BINPRM_FLAGS_PATH_INACCESSIBLE (1 << BINPRM_FLAGS_PATH_INACCESSIBLE_BIT) #define BINPRM_FLAGS_PATH_INACCESSIBLE (1 << BINPRM_FLAGS_PATH_INACCESSIBLE_BIT)