Commit a6f76f23 authored by David Howells's avatar David Howells Committed by James Morris

CRED: Make execve() take advantage of copy-on-write credentials

Make execve() take advantage of copy-on-write credentials, allowing it to set
up the credentials in advance, and then commit the whole lot after the point
of no return.

This patch and the preceding patches have been tested with the LTP SELinux
testsuite.

This patch makes several logical sets of alteration:

 (1) execve().

     The credential bits from struct linux_binprm are, for the most part,
     replaced with a single credentials pointer (bprm->cred).  This means that
     all the creds can be calculated in advance and then applied at the point
     of no return with no possibility of failure.

     I would like to replace bprm->cap_effective with:

	cap_isclear(bprm->cap_effective)

     but this seems impossible due to special behaviour for processes of pid 1
     (they always retain their parent's capability masks where normally they'd
     be changed - see cap_bprm_set_creds()).

     The following sequence of events now happens:

     (a) At the start of do_execve, the current task's cred_exec_mutex is
     	 locked to prevent PTRACE_ATTACH from obsoleting the calculation of
     	 creds that we make.

     (a) prepare_exec_creds() is then called to make a copy of the current
     	 task's credentials and prepare it.  This copy is then assigned to
     	 bprm->cred.

  	 This renders security_bprm_alloc() and security_bprm_free()
     	 unnecessary, and so they've been removed.

     (b) The determination of unsafe execution is now performed immediately
     	 after (a) rather than later on in the code.  The result is stored in
     	 bprm->unsafe for future reference.

     (c) prepare_binprm() is called, possibly multiple times.

     	 (i) This applies the result of set[ug]id binaries to the new creds
     	     attached to bprm->cred.  Personality bit clearance is recorded,
     	     but now deferred on the basis that the exec procedure may yet
     	     fail.

         (ii) This then calls the new security_bprm_set_creds().  This should
	     calculate the new LSM and capability credentials into *bprm->cred.

	     This folds together security_bprm_set() and parts of
	     security_bprm_apply_creds() (these two have been removed).
	     Anything that might fail must be done at this point.

         (iii) bprm->cred_prepared is set to 1.

	     bprm->cred_prepared is 0 on the first pass of the security
	     calculations, and 1 on all subsequent passes.  This allows SELinux
	     in (ii) to base its calculations only on the initial script and
	     not on the interpreter.

     (d) flush_old_exec() is called to commit the task to execution.  This
     	 performs the following steps with regard to credentials:

	 (i) Clear pdeath_signal and set dumpable on certain circumstances that
	     may not be covered by commit_creds().

         (ii) Clear any bits in current->personality that were deferred from
             (c.i).

     (e) install_exec_creds() [compute_creds() as was] is called to install the
     	 new credentials.  This performs the following steps with regard to
     	 credentials:

         (i) Calls security_bprm_committing_creds() to apply any security
             requirements, such as flushing unauthorised files in SELinux, that
             must be done before the credentials are changed.

	     This is made up of bits of security_bprm_apply_creds() and
	     security_bprm_post_apply_creds(), both of which have been removed.
	     This function is not allowed to fail; anything that might fail
	     must have been done in (c.ii).

         (ii) Calls commit_creds() to apply the new credentials in a single
             assignment (more or less).  Possibly pdeath_signal and dumpable
             should be part of struct creds.

	 (iii) Unlocks the task's cred_replace_mutex, thus allowing
	     PTRACE_ATTACH to take place.

         (iv) Clears The bprm->cred pointer as the credentials it was holding
             are now immutable.

         (v) Calls security_bprm_committed_creds() to apply any security
             alterations that must be done after the creds have been changed.
             SELinux uses this to flush signals and signal handlers.

     (f) If an error occurs before (d.i), bprm_free() will call abort_creds()
     	 to destroy the proposed new credentials and will then unlock
     	 cred_replace_mutex.  No changes to the credentials will have been
     	 made.

 (2) LSM interface.

     A number of functions have been changed, added or removed:

     (*) security_bprm_alloc(), ->bprm_alloc_security()
     (*) security_bprm_free(), ->bprm_free_security()

     	 Removed in favour of preparing new credentials and modifying those.

     (*) security_bprm_apply_creds(), ->bprm_apply_creds()
     (*) security_bprm_post_apply_creds(), ->bprm_post_apply_creds()

     	 Removed; split between security_bprm_set_creds(),
     	 security_bprm_committing_creds() and security_bprm_committed_creds().

     (*) security_bprm_set(), ->bprm_set_security()

     	 Removed; folded into security_bprm_set_creds().

     (*) security_bprm_set_creds(), ->bprm_set_creds()

     	 New.  The new credentials in bprm->creds should be checked and set up
     	 as appropriate.  bprm->cred_prepared is 0 on the first call, 1 on the
     	 second and subsequent calls.

     (*) security_bprm_committing_creds(), ->bprm_committing_creds()
     (*) security_bprm_committed_creds(), ->bprm_committed_creds()

     	 New.  Apply the security effects of the new credentials.  This
     	 includes closing unauthorised files in SELinux.  This function may not
     	 fail.  When the former is called, the creds haven't yet been applied
     	 to the process; when the latter is called, they have.

 	 The former may access bprm->cred, the latter may not.

 (3) SELinux.

     SELinux has a number of changes, in addition to those to support the LSM
     interface changes mentioned above:

     (a) The bprm_security_struct struct has been removed in favour of using
     	 the credentials-under-construction approach.

     (c) flush_unauthorized_files() now takes a cred pointer and passes it on
     	 to inode_has_perm(), file_has_perm() and dentry_open().
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Acked-by: default avatarJames Morris <jmorris@namei.org>
Acked-by: default avatarSerge Hallyn <serue@us.ibm.com>
Signed-off-by: default avatarJames Morris <jmorris@namei.org>
parent d84f4f99
......@@ -327,7 +327,7 @@ static int load_aout_binary(struct linux_binprm *bprm, struct pt_regs *regs)
current->mm->cached_hole_size = 0;
current->mm->mmap = NULL;
compute_creds(bprm);
install_exec_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
if (N_MAGIC(ex) == OMAGIC) {
......
......@@ -320,7 +320,7 @@ static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
current->mm->free_area_cache = current->mm->mmap_base;
current->mm->cached_hole_size = 0;
compute_creds(bprm);
install_exec_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
#ifdef __sparc__
if (N_MAGIC(ex) == NMAGIC) {
......
......@@ -956,7 +956,7 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
}
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
compute_creds(bprm);
install_exec_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
retval = create_elf_tables(bprm, &loc->elf_ex,
load_addr, interp_load_addr);
......
......@@ -404,7 +404,7 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm,
current->mm->start_stack = current->mm->start_brk + stack_size;
#endif
compute_creds(bprm);
install_exec_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
if (create_elf_fdpic_tables(bprm, current->mm,
&exec_params, &interp_params) < 0)
......
......@@ -880,7 +880,7 @@ static int load_flat_binary(struct linux_binprm * bprm, struct pt_regs * regs)
(libinfo.lib_list[j].loaded)?
libinfo.lib_list[j].start_data:UNLOADED_LIB;
compute_creds(bprm);
install_exec_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
set_binfmt(&flat_format);
......
......@@ -255,7 +255,7 @@ load_som_binary(struct linux_binprm * bprm, struct pt_regs * regs)
kfree(hpuxhdr);
set_binfmt(&som_format);
compute_creds(bprm);
install_exec_creds(bprm);
setup_arg_pages(bprm, STACK_TOP, EXSTACK_DEFAULT);
create_som_tables(bprm);
......
......@@ -1393,10 +1393,20 @@ int compat_do_execve(char * filename,
if (!bprm)
goto out_ret;
retval = mutex_lock_interruptible(&current->cred_exec_mutex);
if (retval < 0)
goto out_free;
retval = -ENOMEM;
bprm->cred = prepare_exec_creds();
if (!bprm->cred)
goto out_unlock;
check_unsafe_exec(bprm);
file = open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_kfree;
goto out_unlock;
sched_exec();
......@@ -1410,14 +1420,10 @@ int compat_do_execve(char * filename,
bprm->argc = compat_count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out_mm;
goto out;
bprm->envc = compat_count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out_mm;
retval = security_bprm_alloc(bprm);
if (retval)
goto out;
retval = prepare_binprm(bprm);
......@@ -1438,19 +1444,16 @@ int compat_do_execve(char * filename,
goto out;
retval = search_binary_handler(bprm, regs);
if (retval >= 0) {
/* execve success */
security_bprm_free(bprm);
acct_update_integrals(current);
free_bprm(bprm);
return retval;
}
if (retval < 0)
goto out;
out:
if (bprm->security)
security_bprm_free(bprm);
/* execve succeeded */
mutex_unlock(&current->cred_exec_mutex);
acct_update_integrals(current);
free_bprm(bprm);
return retval;
out_mm:
out:
if (bprm->mm)
mmput(bprm->mm);
......@@ -1460,7 +1463,10 @@ int compat_do_execve(char * filename,
fput(bprm->file);
}
out_kfree:
out_unlock:
mutex_unlock(&current->cred_exec_mutex);
out_free:
free_bprm(bprm);
out_ret:
......
......@@ -55,6 +55,7 @@
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/tlb.h>
#include "internal.h"
#ifdef __alpha__
/* for /sbin/loader handling in search_binary_handler() */
......@@ -1007,15 +1008,17 @@ int flush_old_exec(struct linux_binprm * bprm)
*/
current->mm->task_size = TASK_SIZE;
if (bprm->e_uid != current_euid() ||
bprm->e_gid != current_egid()) {
set_dumpable(current->mm, suid_dumpable);
/* install the new credentials */
if (bprm->cred->uid != current_euid() ||
bprm->cred->gid != current_egid()) {
current->pdeath_signal = 0;
} else if (file_permission(bprm->file, MAY_READ) ||
(bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) {
bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP) {
set_dumpable(current->mm, suid_dumpable);
}
current->personality &= ~bprm->per_clear;
/* An exec changes our domain. We are no longer part of the thread
group */
......@@ -1032,13 +1035,50 @@ int flush_old_exec(struct linux_binprm * bprm)
EXPORT_SYMBOL(flush_old_exec);
/*
* install the new credentials for this executable
*/
void install_exec_creds(struct linux_binprm *bprm)
{
security_bprm_committing_creds(bprm);
commit_creds(bprm->cred);
bprm->cred = NULL;
/* cred_exec_mutex must be held at least to this point to prevent
* ptrace_attach() from altering our determination of the task's
* credentials; any time after this it may be unlocked */
security_bprm_committed_creds(bprm);
}
EXPORT_SYMBOL(install_exec_creds);
/*
* determine how safe it is to execute the proposed program
* - the caller must hold current->cred_exec_mutex to protect against
* PTRACE_ATTACH
*/
void check_unsafe_exec(struct linux_binprm *bprm)
{
struct task_struct *p = current;
bprm->unsafe = tracehook_unsafe_exec(p);
if (atomic_read(&p->fs->count) > 1 ||
atomic_read(&p->files->count) > 1 ||
atomic_read(&p->sighand->count) > 1)
bprm->unsafe |= LSM_UNSAFE_SHARE;
}
/*
* Fill the binprm structure from the inode.
* Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
*
* This may be called multiple times for binary chains (scripts for example).
*/
int prepare_binprm(struct linux_binprm *bprm)
{
int mode;
umode_t mode;
struct inode * inode = bprm->file->f_path.dentry->d_inode;
int retval;
......@@ -1046,14 +1086,15 @@ int prepare_binprm(struct linux_binprm *bprm)
if (bprm->file->f_op == NULL)
return -EACCES;
bprm->e_uid = current_euid();
bprm->e_gid = current_egid();
/* clear any previous set[ug]id data from a previous binary */
bprm->cred->euid = current_euid();
bprm->cred->egid = current_egid();
if(!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
/* Set-uid? */
if (mode & S_ISUID) {
current->personality &= ~PER_CLEAR_ON_SETID;
bprm->e_uid = inode->i_uid;
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->euid = inode->i_uid;
}
/* Set-gid? */
......@@ -1063,50 +1104,23 @@ int prepare_binprm(struct linux_binprm *bprm)
* executable.
*/
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
current->personality &= ~PER_CLEAR_ON_SETID;
bprm->e_gid = inode->i_gid;
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->egid = inode->i_gid;
}
}
/* fill in binprm security blob */
retval = security_bprm_set(bprm);
retval = security_bprm_set_creds(bprm);
if (retval)
return retval;
bprm->cred_prepared = 1;
memset(bprm->buf,0,BINPRM_BUF_SIZE);
return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
memset(bprm->buf, 0, BINPRM_BUF_SIZE);
return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}
EXPORT_SYMBOL(prepare_binprm);
static int unsafe_exec(struct task_struct *p)
{
int unsafe = tracehook_unsafe_exec(p);
if (atomic_read(&p->fs->count) > 1 ||
atomic_read(&p->files->count) > 1 ||
atomic_read(&p->sighand->count) > 1)
unsafe |= LSM_UNSAFE_SHARE;
return unsafe;
}
void compute_creds(struct linux_binprm *bprm)
{
int unsafe;
if (bprm->e_uid != current_uid())
current->pdeath_signal = 0;
exec_keys(current);
task_lock(current);
unsafe = unsafe_exec(current);
security_bprm_apply_creds(bprm, unsafe);
task_unlock(current);
security_bprm_post_apply_creds(bprm);
}
EXPORT_SYMBOL(compute_creds);
/*
* Arguments are '\0' separated strings found at the location bprm->p
* points to; chop off the first by relocating brpm->p to right after
......@@ -1259,6 +1273,8 @@ EXPORT_SYMBOL(search_binary_handler);
void free_bprm(struct linux_binprm *bprm)
{
free_arg_pages(bprm);
if (bprm->cred)
abort_creds(bprm->cred);
kfree(bprm);
}
......@@ -1284,10 +1300,20 @@ int do_execve(char * filename,
if (!bprm)
goto out_files;
retval = mutex_lock_interruptible(&current->cred_exec_mutex);
if (retval < 0)
goto out_free;
retval = -ENOMEM;
bprm->cred = prepare_exec_creds();
if (!bprm->cred)
goto out_unlock;
check_unsafe_exec(bprm);
file = open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_kfree;
goto out_unlock;
sched_exec();
......@@ -1301,14 +1327,10 @@ int do_execve(char * filename,
bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out_mm;
goto out;
bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out_mm;
retval = security_bprm_alloc(bprm);
if (retval)
goto out;
retval = prepare_binprm(bprm);
......@@ -1330,21 +1352,18 @@ int do_execve(char * filename,
current->flags &= ~PF_KTHREAD;
retval = search_binary_handler(bprm,regs);
if (retval >= 0) {
/* execve success */
security_bprm_free(bprm);
acct_update_integrals(current);
free_bprm(bprm);
if (displaced)
put_files_struct(displaced);
return retval;
}
if (retval < 0)
goto out;
out:
if (bprm->security)
security_bprm_free(bprm);
/* execve succeeded */
mutex_unlock(&current->cred_exec_mutex);
acct_update_integrals(current);
free_bprm(bprm);
if (displaced)
put_files_struct(displaced);
return retval;
out_mm:
out:
if (bprm->mm)
mmput (bprm->mm);
......@@ -1353,7 +1372,11 @@ int do_execve(char * filename,
allow_write_access(bprm->file);
fput(bprm->file);
}
out_kfree:
out_unlock:
mutex_unlock(&current->cred_exec_mutex);
out_free:
free_bprm(bprm);
out_files:
......
......@@ -10,6 +10,7 @@
*/
struct super_block;
struct linux_binprm;
/*
* block_dev.c
......@@ -39,6 +40,11 @@ static inline int sb_is_blkdev_sb(struct super_block *sb)
*/
extern void __init chrdev_init(void);
/*
* exec.c
*/
extern void check_unsafe_exec(struct linux_binprm *);
/*
* namespace.c
*/
......
......@@ -508,22 +508,6 @@ static inline int audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat)
return 0;
}
/*
* ieieeeeee, an audit function without a return code!
*
* This function might fail! I decided that it didn't matter. We are too late
* to fail the syscall and the information isn't REQUIRED for any purpose. It's
* just nice to have. We should be able to look at past audit logs to figure
* out this process's current cap set along with the fcaps from the PATH record
* and use that to come up with the final set. Yeah, its ugly, but all the info
* is still in the audit log. So I'm not going to bother mentioning we failed
* if we couldn't allocate memory.
*
* If someone changes their mind they could create the aux record earlier and
* then search here and use that earlier allocation. But I don't wanna.
*
* -Eric
*/
static inline int audit_log_bprm_fcaps(struct linux_binprm *bprm,
const struct cred *new,
const struct cred *old)
......
......@@ -35,16 +35,20 @@ struct linux_binprm{
struct mm_struct *mm;
unsigned long p; /* current top of mem */
unsigned int sh_bang:1,
misc_bang:1;
misc_bang:1,
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent's caps anyway */
#ifdef __alpha__
unsigned int taso:1;
#endif
unsigned int recursion_depth;
struct file * file;
int e_uid, e_gid;
kernel_cap_t cap_post_exec_permitted;
bool cap_effective;
void *security;
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc;
char * filename; /* Name of binary as seen by procps */
char * interp; /* Name of the binary really executed. Most
......@@ -101,7 +105,7 @@ extern int setup_arg_pages(struct linux_binprm * bprm,
int executable_stack);
extern int bprm_mm_init(struct linux_binprm *bprm);
extern int copy_strings_kernel(int argc,char ** argv,struct linux_binprm *bprm);
extern void compute_creds(struct linux_binprm *binprm);
extern void install_exec_creds(struct linux_binprm *bprm);
extern int do_coredump(long signr, int exit_code, struct pt_regs * regs);
extern int set_binfmt(struct linux_binfmt *new);
extern void free_bprm(struct linux_binprm *);
......
......@@ -84,8 +84,6 @@ struct thread_group_cred {
struct key *process_keyring; /* keyring private to this process */
struct rcu_head rcu; /* RCU deletion hook */
};
extern void release_tgcred(struct cred *cred);
#endif
/*
......@@ -144,6 +142,7 @@ struct cred {
extern void __put_cred(struct cred *);
extern int copy_creds(struct task_struct *, unsigned long);
extern struct cred *prepare_creds(void);
extern struct cred *prepare_exec_creds(void);
extern struct cred *prepare_usermodehelper_creds(void);
extern int commit_creds(struct cred *);
extern void abort_creds(struct cred *);
......
......@@ -278,7 +278,6 @@ extern ctl_table key_sysctls[];
* the userspace interface
*/
extern int install_thread_keyring_to_cred(struct cred *cred);
extern int exec_keys(struct task_struct *tsk);
extern void key_fsuid_changed(struct task_struct *tsk);
extern void key_fsgid_changed(struct task_struct *tsk);
extern void key_init(void);
......@@ -294,7 +293,6 @@ extern void key_init(void);
#define make_key_ref(k, p) NULL
#define key_ref_to_ptr(k) NULL
#define is_key_possessed(k) 0
#define exec_keys(t) do { } while(0)
#define key_fsuid_changed(t) do { } while(0)
#define key_fsgid_changed(t) do { } while(0)
#define key_init() do { } while(0)
......
......@@ -57,8 +57,7 @@ extern int cap_capset(struct cred *new, const struct cred *old,
const kernel_cap_t *effective,
const kernel_cap_t *inheritable,
const kernel_cap_t *permitted);
extern int cap_bprm_set_security(struct linux_binprm *bprm);
extern int cap_bprm_apply_creds(struct linux_binprm *bprm, int unsafe);
extern int cap_bprm_set_creds(struct linux_binprm *bprm);
extern int cap_bprm_secureexec(struct linux_binprm *bprm);
extern int cap_inode_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags);
......@@ -110,7 +109,7 @@ extern unsigned long mmap_min_addr;
struct sched_param;
struct request_sock;
/* bprm_apply_creds unsafe reasons */
/* bprm->unsafe reasons */
#define LSM_UNSAFE_SHARE 1
#define LSM_UNSAFE_PTRACE 2
#define LSM_UNSAFE_PTRACE_CAP 4
......@@ -154,36 +153,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
*
* Security hooks for program execution operations.
*
* @bprm_alloc_security:
* Allocate and attach a security structure to the @bprm->security field.
* The security field is initialized to NULL when the bprm structure is
* allocated.
* @bprm contains the linux_binprm structure to be modified.
* Return 0 if operation was successful.
* @bprm_free_security:
* @bprm contains the linux_binprm structure to be modified.
* Deallocate and clear the @bprm->security field.
* @bprm_apply_creds:
* Compute and set the security attributes of a process being transformed
* by an execve operation based on the old attributes (current->security)
* and the information saved in @bprm->security by the set_security hook.
* Since this function may return an error, in which case the process will
* be killed. However, it can leave the security attributes of the
* process unchanged if an access failure occurs at this point.
* bprm_apply_creds is called under task_lock. @unsafe indicates various
* reasons why it may be unsafe to change security state.
* @bprm contains the linux_binprm structure.
* @bprm_post_apply_creds:
* Runs after bprm_apply_creds with the task_lock dropped, so that
* functions which cannot be called safely under the task_lock can
* be used. This hook is a good place to perform state changes on
* the process such as closing open file descriptors to which access
* is no longer granted if the attributes were changed.
* Note that a security module might need to save state between
* bprm_apply_creds and bprm_post_apply_creds to store the decision
* on whether the process may proceed.
* @bprm contains the linux_binprm structure.
* @bprm_set_security:
* @bprm_set_creds:
* Save security information in the bprm->security field, typically based
* on information about the bprm->file, for later use by the apply_creds
* hook. This hook may also optionally check permissions (e.g. for
......@@ -196,15 +166,30 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @bprm contains the linux_binprm structure.
* Return 0 if the hook is successful and permission is granted.
* @bprm_check_security:
* This hook mediates the point when a search for a binary handler will
* begin. It allows a check the @bprm->security value which is set in
* the preceding set_security call. The primary difference from
* set_security is that the argv list and envp list are reliably
* available in @bprm. This hook may be called multiple times
* during a single execve; and in each pass set_security is called
* first.
* This hook mediates the point when a search for a binary handler will
* begin. It allows a check the @bprm->security value which is set in the
* preceding set_creds call. The primary difference from set_creds is
* that the argv list and envp list are reliably available in @bprm. This
* hook may be called multiple times during a single execve; and in each
* pass set_creds is called first.
* @bprm contains the linux_binprm structure.
* Return 0 if the hook is successful and permission is granted.
* @bprm_committing_creds:
* Prepare to install the new security attributes of a process being
* transformed by an execve operation, based on the old credentials
* pointed to by @current->cred and the information set in @bprm->cred by
* the bprm_set_creds hook. @bprm points to the linux_binprm structure.
* This hook is a good place to perform state changes on the process such
* as closing open file descriptors to which access will no longer be
* granted when the attributes are changed. This is called immediately
* before commit_creds().
* @bprm_committed_creds:
* Tidy up after the installation of the new security attributes of a
* process being transformed by an execve operation. The new credentials
* have, by this point, been set to @current->cred. @bprm points to the
* linux_binprm structure. This hook is a good place to perform state
* changes on the process such as clearing out non-inheritable signal
* state. This is called immediately after commit_creds().
* @bprm_secureexec:
* Return a boolean value (0 or 1) indicating whether a "secure exec"
* is required. The flag is passed in the auxiliary table
......@@ -1301,13 +1286,11 @@ struct security_operations {
int (*settime) (struct timespec *ts, struct timezone *tz);
int (*vm_enough_memory) (struct mm_struct *mm, long pages);
int (*bprm_alloc_security) (struct linux_binprm *bprm);
void (*bprm_free_security) (struct linux_binprm *bprm);
int (*bprm_apply_creds) (struct linux_binprm *bprm, int unsafe);
void (*bprm_post_apply_creds) (struct linux_binprm *bprm);
int (*bprm_set_security) (struct linux_binprm *bprm);
int (*bprm_set_creds) (struct linux_binprm *bprm);
int (*bprm_check_security) (struct linux_binprm *bprm);
int (*bprm_secureexec) (struct linux_binprm *bprm);
void (*bprm_committing_creds) (struct linux_binprm *bprm);
void (*bprm_committed_creds) (struct linux_binprm *bprm);
int (*sb_alloc_security) (struct super_block *sb);
void (*sb_free_security) (struct super_block *sb);
......@@ -1569,12 +1552,10 @@ int security_settime(struct timespec *ts, struct timezone *tz);
int security_vm_enough_memory(long pages);
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages);
int security_vm_enough_memory_kern(long pages);
int security_bprm_alloc(struct linux_binprm *bprm);
void security_bprm_free(struct linux_binprm *bprm);
int security_bprm_apply_creds(struct linux_binprm *bprm, int unsafe);
void security_bprm_post_apply_creds(struct linux_binprm *bprm);
int security_bprm_set(struct linux_binprm *bprm);
int security_bprm_set_creds(struct linux_binprm *bprm);
int security_bprm_check(struct linux_binprm *bprm);
void security_bprm_committing_creds(struct linux_binprm *bprm);
void security_bprm_committed_creds(struct linux_binprm *bprm);
int security_bprm_secureexec(struct linux_binprm *bprm);
int security_sb_alloc(struct super_block *sb);
void security_sb_free(struct super_block *sb);
......@@ -1812,32 +1793,22 @@ static inline int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
return cap_vm_enough_memory(mm, pages);
}
static inline int security_bprm_alloc(struct linux_binprm *bprm)
{
return 0;
}
static inline void security_bprm_free(struct linux_binprm *bprm)
{ }
static inline int security_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
static inline int security_bprm_set_creds(struct linux_binprm *bprm)
{
return cap_bprm_apply_creds(bprm, unsafe);
return cap_bprm_set_creds(bprm);
}
static inline void security_bprm_post_apply_creds(struct linux_binprm *bprm)
static inline int security_bprm_check(struct linux_binprm *bprm)
{
return;
return 0;
}
static inline int security_bprm_set(struct linux_binprm *bprm)
static inline void security_bprm_committing_creds(struct linux_binprm *bprm)
{
return cap_bprm_set_security(bprm);
}
static inline int security_bprm_check(struct linux_binprm *bprm)
static inline void security_bprm_committed_creds(struct linux_binprm *bprm)
{
return 0;
}
static inline int security_bprm_secureexec(struct linux_binprm *bprm)
......
......@@ -68,7 +68,7 @@ static void release_tgcred_rcu(struct rcu_head *rcu)
/*
* Release a set of thread group credentials.
*/
void release_tgcred(struct cred *cred)
static void release_tgcred(struct cred *cred)
{
#ifdef CONFIG_KEYS
struct thread_group_cred *tgcred = cred->tgcred;
......@@ -163,6 +163,50 @@ struct cred *prepare_creds(void)
}
EXPORT_SYMBOL(prepare_creds);
/*
* Prepare credentials for current to perform an execve()
* - The caller must hold current->cred_exec_mutex
*/
struct cred *prepare_exec_creds(void)
{
struct thread_group_cred *tgcred = NULL;
struct cred *new;
#ifdef CONFIG_KEYS
tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
if (!tgcred)
return NULL;
#endif
new = prepare_creds();
if (!new) {
kfree(tgcred);
return new;
}
#ifdef CONFIG_KEYS
/* newly exec'd tasks don't get a thread keyring */
key_put(new->thread_keyring);
new->thread_keyring = NULL;
/* create a new per-thread-group creds for all this set of threads to
* share */
memcpy(tgcred, new->tgcred, sizeof(struct thread_group_cred));
atomic_set(&tgcred->usage, 1);
spin_lock_init(&tgcred->lock);
/* inherit the session keyring; new process keyring */
key_get(tgcred->session_keyring);
tgcred->process_keyring = NULL;
release_tgcred(new);
new->tgcred = tgcred;
#endif
return new;
}
/*
* prepare new credentials for the usermode helper dispatcher
*/
......
......@@ -32,24 +32,19 @@ static int cap_quota_on(struct dentry *dentry)
return 0;
}
static int cap_bprm_alloc_security(struct linux_binprm *bprm)
static int cap_bprm_check_security (struct linux_binprm *bprm)
{
return 0;
}
static void cap_bprm_free_security(struct linux_binprm *bprm)
static void cap_bprm_committing_creds(struct linux_binprm *bprm)
{
}
static void cap_bprm_post_apply_creds(struct linux_binprm *bprm)
static void cap_bprm_committed_creds(struct linux_binprm *bprm)
{
}
static int cap_bprm_check_security(struct linux_binprm *bprm)
{
return 0;
}
static int cap_sb_alloc_security(struct super_block *sb)
{
return 0;
......@@ -827,11 +822,9 @@ void security_fixup_ops(struct security_operations *ops)
set_to_cap_if_null(ops, syslog);
set_to_cap_if_null(ops, settime);
set_to_cap_if_null(ops, vm_enough_memory);
set_to_cap_if_null(ops, bprm_alloc_security);
set_to_cap_if_null(ops, bprm_free_security);
set_to_cap_if_null(ops, bprm_apply_creds);
set_to_cap_if_null(ops, bprm_post_apply_creds);
set_to_cap_if_null(ops, bprm_set_security);
set_to_cap_if_null(ops, bprm_set_creds);
set_to_cap_if_null(ops, bprm_committing_creds);
set_to_cap_if_null(ops, bprm_committed_creds);
set_to_cap_if_null(ops, bprm_check_security);
set_to_cap_if_null(ops, bprm_secureexec);
set_to_cap_if_null(ops, sb_alloc_security);
......
......@@ -167,7 +167,7 @@ int cap_capset(struct cred *new,
static inline void bprm_clear_caps(struct linux_binprm *bprm)
{
cap_clear(bprm->cap_post_exec_permitted);
cap_clear(bprm->cred->cap_permitted);
bprm->cap_effective = false;
}
......@@ -198,15 +198,15 @@ int cap_inode_killpriv(struct dentry *dentry)
}
static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,
struct linux_binprm *bprm)
struct linux_binprm *bprm,
bool *effective)
{
struct cred *new = bprm->cred;
unsigned i;
int ret = 0;
if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE)
bprm->cap_effective = true;
else
bprm->cap_effective = false;
*effective = true;
CAP_FOR_EACH_U32(i) {
__u32 permitted = caps->permitted.cap[i];
......@@ -215,16 +215,13 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,
/*
* pP' = (X & fP) | (pI & fI)
*/
bprm->cap_post_exec_permitted.cap[i] =
(current->cred->cap_bset.cap[i] & permitted) |
(current->cred->cap_inheritable.cap[i] & inheritable);
new->cap_permitted.cap[i] =
(new->cap_bset.cap[i] & permitted) |
(new->cap_inheritable.cap[i] & inheritable);
if (permitted & ~bprm->cap_post_exec_permitted.cap[i]) {
/*
* insufficient to execute correctly
*/
if (permitted & ~new->cap_permitted.cap[i])
/* insufficient to execute correctly */
ret = -EPERM;
}
}
/*
......@@ -232,7 +229,7 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,
* do not have enough capabilities, we return an error if they are
* missing some "forced" (aka file-permitted) capabilities.
*/
return bprm->cap_effective ? ret : 0;
return *effective ? ret : 0;
}
int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps)
......@@ -250,10 +247,9 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data
size = inode->i_op->getxattr((struct dentry *)dentry, XATTR_NAME_CAPS, &caps,
XATTR_CAPS_SZ);
if (size == -ENODATA || size == -EOPNOTSUPP) {
if (size == -ENODATA || size == -EOPNOTSUPP)
/* no data, that's ok */
return -ENODATA;
}
if (size < 0)
return size;
......@@ -262,7 +258,7 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data
cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps.magic_etc);
switch ((magic_etc & VFS_CAP_REVISION_MASK)) {
switch (magic_etc & VFS_CAP_REVISION_MASK) {
case VFS_CAP_REVISION_1:
if (size != XATTR_CAPS_SZ_1)
return -EINVAL;
......@@ -283,11 +279,12 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data
cpu_caps->permitted.cap[i] = le32_to_cpu(caps.data[i].permitted);
cpu_caps->inheritable.cap[i] = le32_to_cpu(caps.data[i].inheritable);
}
return 0;
}
/* Locate any VFS capabilities: */
static int get_file_caps(struct linux_binprm *bprm)
static int get_file_caps(struct linux_binprm *bprm, bool *effective)
{
struct dentry *dentry;
int rc = 0;
......@@ -313,7 +310,10 @@ static int get_file_caps(struct linux_binprm *bprm)
goto out;
}
rc = bprm_caps_from_vfs_caps(&vcaps, bprm);
rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective);
if (rc == -EINVAL)
printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n",
__func__, rc, bprm->filename);
out:
dput(dentry);
......@@ -334,18 +334,27 @@ int cap_inode_killpriv(struct dentry *dentry)
return 0;
}
static inline int get_file_caps(struct linux_binprm *bprm)
static inline int get_file_caps(struct linux_binprm *bprm, bool *effective)
{
bprm_clear_caps(bprm);
return 0;
}
#endif
int cap_bprm_set_security (struct linux_binprm *bprm)
/*
* set up the new credentials for an exec'd task
*/
int cap_bprm_set_creds(struct linux_binprm *bprm)
{
const struct cred *old = current_cred();
struct cred *new = bprm->cred;
bool effective;
int ret;
ret = get_file_caps(bprm);
effective = false;
ret = get_file_caps(bprm, &effective);
if (ret < 0)
return ret;
if (!issecure(SECURE_NOROOT)) {
/*
......@@ -353,63 +362,47 @@ int cap_bprm_set_security (struct linux_binprm *bprm)
* executables under compatibility mode, we override the
* capability sets for the file.
*
* If only the real uid is 0, we do not set the effective
* bit.
* If only the real uid is 0, we do not set the effective bit.
*/
if (bprm->e_uid == 0 || current_uid() == 0) {
if (new->euid == 0 || new->uid == 0) {
/* pP' = (cap_bset & ~0) | (pI & ~0) */
bprm->cap_post_exec_permitted = cap_combine(
current->cred->cap_bset,
current->cred->cap_inheritable);
bprm->cap_effective = (bprm->e_uid == 0);
ret = 0;
new->cap_permitted = cap_combine(old->cap_bset,
old->cap_inheritable);
}
if (new->euid == 0)
effective = true;
}
return ret;
}
int cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
{
const struct cred *old = current_cred();
struct cred *new;
new = prepare_creds();
if (!new)
return -ENOMEM;
if (bprm->e_uid != old->uid || bprm->e_gid != old->gid ||
!cap_issubset(bprm->cap_post_exec_permitted,
old->cap_permitted)) {
set_dumpable(current->mm, suid_dumpable);
current->pdeath_signal = 0;
if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
if (!capable(CAP_SETUID)) {
bprm->e_uid = old->uid;
bprm->e_gid = old->gid;
}
if (cap_limit_ptraced_target()) {
bprm->cap_post_exec_permitted = cap_intersect(
bprm->cap_post_exec_permitted,
new->cap_permitted);
}
/* Don't let someone trace a set[ug]id/setpcap binary with the revised
* credentials unless they have the appropriate permit
*/
if ((new->euid != old->uid ||
new->egid != old->gid ||
!cap_issubset(new->cap_permitted, old->cap_permitted)) &&
bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
/* downgrade; they get no more than they had, and maybe less */
if (!capable(CAP_SETUID)) {
new->euid = new->uid;
new->egid = new->gid;
}
if (cap_limit_ptraced_target())
new->cap_permitted = cap_intersect(new->cap_permitted,
old->cap_permitted);
}
new->suid = new->euid = new->fsuid = bprm->e_uid;
new->sgid = new->egid = new->fsgid = bprm->e_gid;
new->suid = new->fsuid = new->euid;
new->sgid = new->fsgid = new->egid;
/* For init, we want to retain the capabilities set
* in the init_task struct. Thus we skip the usual
* capability rules */
/* For init, we want to retain the capabilities set in the initial
* task. Thus we skip the usual capability rules
*/
if (!is_global_init(current)) {
new->cap_permitted = bprm->cap_post_exec_permitted;
if (bprm->cap_effective)
new->cap_effective = bprm->cap_post_exec_permitted;
if (effective)
new->cap_effective = new->cap_permitted;
else
cap_clear(new->cap_effective);
}
bprm->cap_effective = effective;
/*
* Audit candidate if current->cap_effective is set
......@@ -425,23 +418,31 @@ int cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
*/
if (!cap_isclear(new->cap_effective)) {
if (!cap_issubset(CAP_FULL_SET, new->cap_effective) ||
bprm->e_uid != 0 || new->uid != 0 ||
issecure(SECURE_NOROOT))
audit_log_bprm_fcaps(bprm, new, old);
new->euid != 0 || new->uid != 0 ||
issecure(SECURE_NOROOT)) {
ret = audit_log_bprm_fcaps(bprm, new, old);
if (ret < 0)
return ret;
}
}
new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS);
return commit_creds(new);
return 0;
}
int cap_bprm_secureexec (struct linux_binprm *bprm)
/*
* determine whether a secure execution is required
* - the creds have been committed at this point, and are no longer available
* through bprm
*/
int cap_bprm_secureexec(struct linux_binprm *bprm)
{
const struct cred *cred = current_cred();
if (cred->uid != 0) {
if (bprm->cap_effective)
return 1;
if (!cap_isclear(bprm->cap_post_exec_permitted))
if (!cap_isclear(cred->cap_permitted))
return 1;
}
......@@ -477,7 +478,7 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name)
}
/* moved from kernel/sys.c. */
/*
/*
* cap_emulate_setxuid() fixes the effective / permitted capabilities of
* a process after a call to setuid, setreuid, or setresuid.
*
......@@ -491,10 +492,10 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name)
* 3) When set*uiding _from_ euid != 0 _to_ euid == 0, the effective
* capabilities are set to the permitted capabilities.
*
* fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should
* fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should
* never happen.
*
* -astor
* -astor
*
* cevans - New behaviour, Oct '99
* A process may, via prctl(), elect to keep its capabilities when it
......@@ -751,4 +752,3 @@ int cap_vm_enough_memory(struct mm_struct *mm, long pages)
cap_sys_admin = 1;
return __vm_enough_memory(mm, pages, cap_sys_admin);
}
......@@ -274,48 +274,6 @@ static int install_session_keyring(struct key *keyring)
return commit_creds(new);
}
/*****************************************************************************/
/*
* deal with execve()
*/
int exec_keys(struct task_struct *tsk)
{
struct thread_group_cred *tgcred = NULL;
struct cred *new;
#ifdef CONFIG_KEYS
tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
if (!tgcred)
return -ENOMEM;
#endif
new = prepare_creds();
if (new < 0)
return -ENOMEM;
/* newly exec'd tasks don't get a thread keyring */
key_put(new->thread_keyring);
new->thread_keyring = NULL;
/* create a new per-thread-group creds for all this set of threads to
* share */
memcpy(tgcred, new->tgcred, sizeof(struct thread_group_cred));
atomic_set(&tgcred->usage, 1);
spin_lock_init(&tgcred->lock);
/* inherit the session keyring; new process keyring */
key_get(tgcred->session_keyring);
tgcred->process_keyring = NULL;
release_tgcred(new);
new->tgcred = tgcred;
commit_creds(new);
return 0;
} /* end exec_keys() */
/*****************************************************************************/
/*
* the filesystem user ID changed
......
......@@ -55,9 +55,9 @@ static int rootplug_bprm_check_security (struct linux_binprm *bprm)
struct usb_device *dev;
root_dbg("file %s, e_uid = %d, e_gid = %d\n",
bprm->filename, bprm->e_uid, bprm->e_gid);
bprm->filename, bprm->cred->euid, bprm->cred->egid);
if (bprm->e_gid == 0) {
if (bprm->cred->egid == 0) {
dev = usb_find_device(vendor_id, product_id);
if (!dev) {
root_dbg("e_gid = 0, and device not found, "
......@@ -75,15 +75,12 @@ static struct security_operations rootplug_security_ops = {
.ptrace_may_access = cap_ptrace_may_access,
.ptrace_traceme = cap_ptrace_traceme,
.capget = cap_capget,
.capset_check = cap_capset_check,
.capset_set = cap_capset_set,
.capset = cap_capset,
.capable = cap_capable,
.bprm_apply_creds = cap_bprm_apply_creds,
.bprm_set_security = cap_bprm_set_security,
.bprm_set_creds = cap_bprm_set_creds,
.task_post_setuid = cap_task_post_setuid,
.task_reparent_to_init = cap_task_reparent_to_init,
.task_fix_setuid = cap_task_fix_setuid,
.task_prctl = cap_task_prctl,
.bprm_check_security = rootplug_bprm_check_security,
......
......@@ -213,34 +213,24 @@ int security_vm_enough_memory_kern(long pages)
return security_ops->vm_enough_memory(current->mm, pages);
}
int security_bprm_alloc(struct linux_binprm *bprm)
int security_bprm_set_creds(struct linux_binprm *bprm)
{
return security_ops->bprm_alloc_security(bprm);
return security_ops->bprm_set_creds(bprm);
}
void security_bprm_free(struct linux_binprm *bprm)
{
security_ops->bprm_free_security(bprm);
}
int security_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
{
return security_ops->bprm_apply_creds(bprm, unsafe);
}
void security_bprm_post_apply_creds(struct linux_binprm *bprm)
int security_bprm_check(struct linux_binprm *bprm)
{
security_ops->bprm_post_apply_creds(bprm);
return security_ops->bprm_check_security(bprm);
}
int security_bprm_set(struct linux_binprm *bprm)
void security_bprm_committing_creds(struct linux_binprm *bprm)
{
return security_ops->bprm_set_security(bprm);
return security_ops->bprm_committing_creds(bprm);
}
int security_bprm_check(struct linux_binprm *bprm)
void security_bprm_committed_creds(struct linux_binprm *bprm)
{
return security_ops->bprm_check_security(bprm);
return security_ops->bprm_committed_creds(bprm);
}
int security_bprm_secureexec(struct linux_binprm *bprm)
......
This diff is collapsed.
......@@ -77,17 +77,6 @@ struct ipc_security_struct {
u32 sid; /* SID of IPC resource */
};
struct bprm_security_struct {
u32 sid; /* SID for transformed process */
unsigned char set;
/*
* unsafe is used to share failure information from bprm_apply_creds()
* to bprm_post_apply_creds().
*/
char unsafe;
};
struct netif_security_struct {
int ifindex; /* device index */
u32 sid; /* SID for this interface */
......
......@@ -2596,8 +2596,7 @@ struct security_operations smack_ops = {
.settime = cap_settime,
.vm_enough_memory = cap_vm_enough_memory,
.bprm_apply_creds = cap_bprm_apply_creds,
.bprm_set_security = cap_bprm_set_security,
.bprm_set_creds = cap_bprm_set_creds,
.bprm_secureexec = cap_bprm_secureexec,
.sb_alloc_security = smack_sb_alloc_security,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment