Skip to content

Commit

Permalink
execve: use 'struct filename *' for executable name passing
Browse files Browse the repository at this point in the history
This changes 'do_execve()' to get the executable name as a 'struct
filename', and to free it when it is done.  This is what the normal
users want, and it simplifies and streamlines their error handling.

The controlled lifetime of the executable name also fixes a
use-after-free problem with the trace_sched_process_exec tracepoint: the
lifetime of the passed-in string for kernel users was not at all
obvious, and the user-mode helper code used UMH_WAIT_EXEC to serialize
the pathname allocation lifetime with the execve() having finished,
which in turn meant that the trace point that happened after
mm_release() of the old process VM ended up using already free'd memory.

To solve the kernel string lifetime issue, this simply introduces
"getname_kernel()" that works like the normal user-space getname()
function, except with the source coming from kernel memory.

As Oleg points out, this also means that we could drop the tcomm[] array
from 'struct linux_binprm', since the pathname lifetime now covers
setup_new_exec().  That would be a separate cleanup.

Reported-by: Igor Zhbanov <[email protected]>
Tested-by: Steven Rostedt <[email protected]>
Cc: Oleg Nesterov <[email protected]>
Cc: Al Viro <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
torvalds committed Feb 5, 2014
1 parent 878a876 commit c4ad8f9
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 43 deletions.
15 changes: 1 addition & 14 deletions arch/parisc/hpux/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,9 @@

int hpux_execve(struct pt_regs *regs)
{
int error;
struct filename *filename;

filename = getname((const char __user *) regs->gr[26]);
error = PTR_ERR(filename);
if (IS_ERR(filename))
goto out;

error = do_execve(filename->name,
return do_execve(getname((const char __user *) regs->gr[26]),
(const char __user *const __user *) regs->gr[25],
(const char __user *const __user *) regs->gr[24]);

putname(filename);

out:
return error;
}

struct hpux_dirent {
Expand Down
45 changes: 21 additions & 24 deletions fs/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -748,19 +748,18 @@ EXPORT_SYMBOL(setup_arg_pages);

#endif /* CONFIG_MMU */

struct file *open_exec(const char *name)
static struct file *do_open_exec(struct filename *name)
{
struct file *file;
int err;
struct filename tmp = { .name = name };
static const struct open_flags open_exec_flags = {
.open_flag = O_LARGEFILE | O_RDONLY | __FMODE_EXEC,
.acc_mode = MAY_EXEC | MAY_OPEN,
.intent = LOOKUP_OPEN,
.lookup_flags = LOOKUP_FOLLOW,
};

file = do_filp_open(AT_FDCWD, &tmp, &open_exec_flags);
file = do_filp_open(AT_FDCWD, name, &open_exec_flags);
if (IS_ERR(file))
goto out;

Expand All @@ -784,6 +783,12 @@ struct file *open_exec(const char *name)
fput(file);
return ERR_PTR(err);
}

struct file *open_exec(const char *name)
{
struct filename tmp = { .name = name };
return do_open_exec(&tmp);
}
EXPORT_SYMBOL(open_exec);

int kernel_read(struct file *file, loff_t offset,
Expand Down Expand Up @@ -1162,7 +1167,7 @@ int prepare_bprm_creds(struct linux_binprm *bprm)
return -ENOMEM;
}

void free_bprm(struct linux_binprm *bprm)
static void free_bprm(struct linux_binprm *bprm)
{
free_arg_pages(bprm);
if (bprm->cred) {
Expand Down Expand Up @@ -1432,7 +1437,7 @@ static int exec_binprm(struct linux_binprm *bprm)
/*
* sys_execve() executes a new program.
*/
static int do_execve_common(const char *filename,
static int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
Expand All @@ -1441,6 +1446,9 @@ static int do_execve_common(const char *filename,
struct files_struct *displaced;
int retval;

if (IS_ERR(filename))
return PTR_ERR(filename);

/*
* We move the actual failure in case of RLIMIT_NPROC excess from
* set*uid() to execve() because too many poorly written programs
Expand Down Expand Up @@ -1473,16 +1481,15 @@ static int do_execve_common(const char *filename,
check_unsafe_exec(bprm);
current->in_execve = 1;

file = open_exec(filename);
file = do_open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_unmark;

sched_exec();

bprm->file = file;
bprm->filename = filename;
bprm->interp = filename;
bprm->filename = bprm->interp = filename->name;

retval = bprm_mm_init(bprm);
if (retval)
Expand Down Expand Up @@ -1523,6 +1530,7 @@ static int do_execve_common(const char *filename,
acct_update_integrals(current);
task_numa_free(current);
free_bprm(bprm);
putname(filename);
if (displaced)
put_files_struct(displaced);
return retval;
Expand All @@ -1544,10 +1552,11 @@ static int do_execve_common(const char *filename,
if (displaced)
reset_files_struct(displaced);
out_ret:
putname(filename);
return retval;
}

int do_execve(const char *filename,
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
Expand All @@ -1557,7 +1566,7 @@ int do_execve(const char *filename,
}

#ifdef CONFIG_COMPAT
static int compat_do_execve(const char *filename,
static int compat_do_execve(struct filename *filename,
const compat_uptr_t __user *__argv,
const compat_uptr_t __user *__envp)
{
Expand Down Expand Up @@ -1607,25 +1616,13 @@ SYSCALL_DEFINE3(execve,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
struct filename *path = getname(filename);
int error = PTR_ERR(path);
if (!IS_ERR(path)) {
error = do_execve(path->name, argv, envp);
putname(path);
}
return error;
return do_execve(getname(filename), argv, envp);
}
#ifdef CONFIG_COMPAT
asmlinkage long compat_sys_execve(const char __user * filename,
const compat_uptr_t __user * argv,
const compat_uptr_t __user * envp)
{
struct filename *path = getname(filename);
int error = PTR_ERR(path);
if (!IS_ERR(path)) {
error = compat_do_execve(path->name, argv, envp);
putname(path);
}
return error;
return compat_do_execve(getname(filename), argv, envp);
}
#endif
30 changes: 30 additions & 0 deletions fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ getname_flags(const char __user *filename, int flags, int *empty)
goto error;

result->uptr = filename;
result->aname = NULL;
audit_getname(result);
return result;

Expand All @@ -210,6 +211,35 @@ getname(const char __user * filename)
return getname_flags(filename, 0, NULL);
}

/*
* The "getname_kernel()" interface doesn't do pathnames longer
* than EMBEDDED_NAME_MAX. Deal with it - you're a kernel user.
*/
struct filename *
getname_kernel(const char * filename)
{
struct filename *result;
char *kname;
int len;

len = strlen(filename);
if (len >= EMBEDDED_NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);

result = __getname();
if (unlikely(!result))
return ERR_PTR(-ENOMEM);

kname = (char *)result + sizeof(*result);
result->name = kname;
result->uptr = NULL;
result->aname = NULL;
result->separate = false;

strlcpy(kname, filename, EMBEDDED_NAME_MAX);
return result;
}

#ifdef CONFIG_AUDITSYSCALL
void putname(struct filename *name)
{
Expand Down
1 change: 0 additions & 1 deletion include/linux/binfmts.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ extern int copy_strings_kernel(int argc, const char *const *argv,
extern int prepare_bprm_creds(struct linux_binprm *bprm);
extern void install_exec_creds(struct linux_binprm *bprm);
extern void set_binfmt(struct linux_binfmt *new);
extern void free_bprm(struct linux_binprm *);
extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t);

#endif /* _LINUX_BINFMTS_H */
1 change: 1 addition & 0 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2079,6 +2079,7 @@ extern struct file * dentry_open(const struct path *, int, const struct cred *);
extern int filp_close(struct file *, fl_owner_t id);

extern struct filename *getname(const char __user *);
extern struct filename *getname_kernel(const char *);

enum {
FILE_CREATED = 1,
Expand Down
3 changes: 2 additions & 1 deletion include/linux/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ struct bio_list;
struct fs_struct;
struct perf_event_context;
struct blk_plug;
struct filename;

/*
* List of flags we want to share for kernel threads,
Expand Down Expand Up @@ -2311,7 +2312,7 @@ extern void do_group_exit(int);
extern int allow_signal(int);
extern int disallow_signal(int);

extern int do_execve(const char *,
extern int do_execve(struct filename *,
const char __user * const __user *,
const char __user * const __user *);
extern long do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *);
Expand Down
2 changes: 1 addition & 1 deletion init/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ void __init load_default_modules(void)
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(init_filename,
return do_execve(getname_kernel(init_filename),
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
Expand Down
2 changes: 1 addition & 1 deletion kernel/auditsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1719,7 +1719,7 @@ void audit_putname(struct filename *name)
struct audit_context *context = current->audit_context;

BUG_ON(!context);
if (!context->in_syscall) {
if (!name->aname || !context->in_syscall) {
#if AUDIT_DEBUG == 2
printk(KERN_ERR "%s:%d(:%d): final_putname(%p)\n",
__FILE__, __LINE__, context->serial, name);
Expand Down
2 changes: 1 addition & 1 deletion kernel/kmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ static int ____call_usermodehelper(void *data)

commit_creds(new);

retval = do_execve(sub_info->path,
retval = do_execve(getname_kernel(sub_info->path),
(const char __user *const __user *)sub_info->argv,
(const char __user *const __user *)sub_info->envp);
if (!retval)
Expand Down

0 comments on commit c4ad8f9

Please sign in to comment.