Commit d6e71144 authored by Alan Cox's avatar Alan Cox Committed by Linus Torvalds

[PATCH] setuid core dump

Add a new `suid_dumpable' sysctl:

This value can be used to query and set the core dump mode for setuid
or otherwise protected/tainted binaries. The modes are

0 - (default) - traditional behaviour.  Any process which has changed
    privilege levels or is execute only will not be dumped

1 - (debug) - all processes dump core when possible.  The core dump is
    owned by the current user and no security is applied.  This is intended
    for system debugging situations only.  Ptrace is unchecked.

2 - (suidsafe) - any binary which normally would not be dumped is dumped
    readable by root only.  This allows the end user to remove such a dump but
    not access it directly.  For security reasons core dumps in this mode will
    not overwrite one another or other files.  This mode is appropriate when
    adminstrators are attempting to debug problems in a normal environment.

(akpm:

> > +EXPORT_SYMBOL(suid_dumpable);
>
> EXPORT_SYMBOL_GPL?

No problem to me.

> >  	if (current->euid == current->uid && current->egid == current->gid)
> >  		current->mm->dumpable = 1;
>
> Should this be SUID_DUMP_USER?

Actually the feedback I had from last time was that the SUID_ defines
should go because its clearer to follow the numbers. They can go
everywhere (and there are lots of places where dumpable is tested/used
as a bool in untouched code)

> Maybe this should be renamed to `dump_policy' or something.  Doing that
> would help us catch any code which isn't using the #defines, too.

Fair comment. The patch was designed to be easy to maintain for Red Hat
rather than for merging. Changing that field would create a gigantic
diff because it is used all over the place.

)
Signed-off-by: default avatarAlan Cox <alan@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 8b0914ea
...@@ -49,6 +49,7 @@ show up in /proc/sys/kernel: ...@@ -49,6 +49,7 @@ show up in /proc/sys/kernel:
- shmmax [ sysv ipc ] - shmmax [ sysv ipc ]
- shmmni - shmmni
- stop-a [ SPARC only ] - stop-a [ SPARC only ]
- suid_dumpable
- sysrq ==> Documentation/sysrq.txt - sysrq ==> Documentation/sysrq.txt
- tainted - tainted
- threads-max - threads-max
...@@ -300,6 +301,25 @@ kernel. This value defaults to SHMMAX. ...@@ -300,6 +301,25 @@ kernel. This value defaults to SHMMAX.
============================================================== ==============================================================
suid_dumpable:
This value can be used to query and set the core dump mode for setuid
or otherwise protected/tainted binaries. The modes are
0 - (default) - traditional behaviour. Any process which has changed
privilege levels or is execute only will not be dumped
1 - (debug) - all processes dump core when possible. The core dump is
owned by the current user and no security is applied. This is
intended for system debugging situations only. Ptrace is unchecked.
2 - (suidsafe) - any binary which normally would not be dumped is dumped
readable by root only. This allows the end user to remove
such a dump but not access it directly. For security reasons
core dumps in this mode will not overwrite one another or
other files. This mode is appropriate when adminstrators are
attempting to debug problems in a normal environment.
==============================================================
tainted: tainted:
Non-zero if the kernel has been tainted. Numeric values, which Non-zero if the kernel has been tainted. Numeric values, which
......
...@@ -58,6 +58,9 @@ ...@@ -58,6 +58,9 @@
int core_uses_pid; int core_uses_pid;
char core_pattern[65] = "core"; char core_pattern[65] = "core";
int suid_dumpable = 0;
EXPORT_SYMBOL(suid_dumpable);
/* The maximal length of core_pattern is also specified in sysctl.c */ /* The maximal length of core_pattern is also specified in sysctl.c */
static struct linux_binfmt *formats; static struct linux_binfmt *formats;
...@@ -864,6 +867,9 @@ int flush_old_exec(struct linux_binprm * bprm) ...@@ -864,6 +867,9 @@ int flush_old_exec(struct linux_binprm * bprm)
if (current->euid == current->uid && current->egid == current->gid) if (current->euid == current->uid && current->egid == current->gid)
current->mm->dumpable = 1; current->mm->dumpable = 1;
else
current->mm->dumpable = suid_dumpable;
name = bprm->filename; name = bprm->filename;
/* Copies the binary name from after last slash */ /* Copies the binary name from after last slash */
...@@ -884,7 +890,7 @@ int flush_old_exec(struct linux_binprm * bprm) ...@@ -884,7 +890,7 @@ int flush_old_exec(struct linux_binprm * bprm)
permission(bprm->file->f_dentry->d_inode,MAY_READ, NULL) || permission(bprm->file->f_dentry->d_inode,MAY_READ, NULL) ||
(bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) { (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) {
suid_keys(current); suid_keys(current);
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
} }
/* An exec changes our domain. We are no longer part of the thread /* An exec changes our domain. We are no longer part of the thread
...@@ -1432,6 +1438,8 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs) ...@@ -1432,6 +1438,8 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
struct inode * inode; struct inode * inode;
struct file * file; struct file * file;
int retval = 0; int retval = 0;
int fsuid = current->fsuid;
int flag = 0;
binfmt = current->binfmt; binfmt = current->binfmt;
if (!binfmt || !binfmt->core_dump) if (!binfmt || !binfmt->core_dump)
...@@ -1441,6 +1449,16 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs) ...@@ -1441,6 +1449,16 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
up_write(&mm->mmap_sem); up_write(&mm->mmap_sem);
goto fail; goto fail;
} }
/*
* We cannot trust fsuid as being the "true" uid of the
* process nor do we know its entire history. We only know it
* was tainted so we dump it as root in mode 2.
*/
if (mm->dumpable == 2) { /* Setuid core dump mode */
flag = O_EXCL; /* Stop rewrite attacks */
current->fsuid = 0; /* Dump root private */
}
mm->dumpable = 0; mm->dumpable = 0;
init_completion(&mm->core_done); init_completion(&mm->core_done);
spin_lock_irq(&current->sighand->siglock); spin_lock_irq(&current->sighand->siglock);
...@@ -1466,7 +1484,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs) ...@@ -1466,7 +1484,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
lock_kernel(); lock_kernel();
format_corename(corename, core_pattern, signr); format_corename(corename, core_pattern, signr);
unlock_kernel(); unlock_kernel();
file = filp_open(corename, O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE, 0600); file = filp_open(corename, O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag, 0600);
if (IS_ERR(file)) if (IS_ERR(file))
goto fail_unlock; goto fail_unlock;
inode = file->f_dentry->d_inode; inode = file->f_dentry->d_inode;
...@@ -1491,6 +1509,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs) ...@@ -1491,6 +1509,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
close_fail: close_fail:
filp_close(file, NULL); filp_close(file, NULL);
fail_unlock: fail_unlock:
current->fsuid = fsuid;
complete_all(&mm->core_done); complete_all(&mm->core_done);
fail: fail:
return retval; return retval;
......
...@@ -314,7 +314,7 @@ static int may_ptrace_attach(struct task_struct *task) ...@@ -314,7 +314,7 @@ static int may_ptrace_attach(struct task_struct *task)
(current->gid != task->gid)) && !capable(CAP_SYS_PTRACE)) (current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))
goto out; goto out;
rmb(); rmb();
if (!task->mm->dumpable && !capable(CAP_SYS_PTRACE)) if (task->mm->dumpable != 1 && !capable(CAP_SYS_PTRACE))
goto out; goto out;
if (security_ptrace(current, task)) if (security_ptrace(current, task))
goto out; goto out;
...@@ -1113,7 +1113,9 @@ static int task_dumpable(struct task_struct *task) ...@@ -1113,7 +1113,9 @@ static int task_dumpable(struct task_struct *task)
if (mm) if (mm)
dumpable = mm->dumpable; dumpable = mm->dumpable;
task_unlock(task); task_unlock(task);
return dumpable; if(dumpable == 1)
return 1;
return 0;
} }
......
...@@ -69,6 +69,11 @@ extern void remove_arg_zero(struct linux_binprm *); ...@@ -69,6 +69,11 @@ extern void remove_arg_zero(struct linux_binprm *);
extern int search_binary_handler(struct linux_binprm *,struct pt_regs *); extern int search_binary_handler(struct linux_binprm *,struct pt_regs *);
extern int flush_old_exec(struct linux_binprm * bprm); extern int flush_old_exec(struct linux_binprm * bprm);
extern int suid_dumpable;
#define SUID_DUMP_DISABLE 0 /* No setuid dumping */
#define SUID_DUMP_USER 1 /* Dump as user of process */
#define SUID_DUMP_ROOT 2 /* Dump as root */
/* Stack area protections */ /* Stack area protections */
#define EXSTACK_DEFAULT 0 /* Whatever the arch defaults to */ #define EXSTACK_DEFAULT 0 /* Whatever the arch defaults to */
#define EXSTACK_DISABLE_X 1 /* Disable executable stacks */ #define EXSTACK_DISABLE_X 1 /* Disable executable stacks */
......
...@@ -246,7 +246,7 @@ struct mm_struct { ...@@ -246,7 +246,7 @@ struct mm_struct {
unsigned long saved_auxv[42]; /* for /proc/PID/auxv */ unsigned long saved_auxv[42]; /* for /proc/PID/auxv */
unsigned dumpable:1; unsigned dumpable:2;
cpumask_t cpu_vm_mask; cpumask_t cpu_vm_mask;
/* Architecture-specific MM context */ /* Architecture-specific MM context */
......
...@@ -136,6 +136,7 @@ enum ...@@ -136,6 +136,7 @@ enum
KERN_UNKNOWN_NMI_PANIC=66, /* int: unknown nmi panic flag */ KERN_UNKNOWN_NMI_PANIC=66, /* int: unknown nmi panic flag */
KERN_BOOTLOADER_TYPE=67, /* int: boot loader type */ KERN_BOOTLOADER_TYPE=67, /* int: boot loader type */
KERN_RANDOMIZE=68, /* int: randomize virtual address space */ KERN_RANDOMIZE=68, /* int: randomize virtual address space */
KERN_SETUID_DUMPABLE=69, /* int: behaviour of dumps for setuid core */
}; };
......
...@@ -525,7 +525,7 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid) ...@@ -525,7 +525,7 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
} }
if (new_egid != old_egid) if (new_egid != old_egid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
if (rgid != (gid_t) -1 || if (rgid != (gid_t) -1 ||
...@@ -556,7 +556,7 @@ asmlinkage long sys_setgid(gid_t gid) ...@@ -556,7 +556,7 @@ asmlinkage long sys_setgid(gid_t gid)
{ {
if(old_egid != gid) if(old_egid != gid)
{ {
current->mm->dumpable=0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->gid = current->egid = current->sgid = current->fsgid = gid; current->gid = current->egid = current->sgid = current->fsgid = gid;
...@@ -565,7 +565,7 @@ asmlinkage long sys_setgid(gid_t gid) ...@@ -565,7 +565,7 @@ asmlinkage long sys_setgid(gid_t gid)
{ {
if(old_egid != gid) if(old_egid != gid)
{ {
current->mm->dumpable=0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->egid = current->fsgid = gid; current->egid = current->fsgid = gid;
...@@ -596,7 +596,7 @@ static int set_user(uid_t new_ruid, int dumpclear) ...@@ -596,7 +596,7 @@ static int set_user(uid_t new_ruid, int dumpclear)
if(dumpclear) if(dumpclear)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->uid = new_ruid; current->uid = new_ruid;
...@@ -653,7 +653,7 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid) ...@@ -653,7 +653,7 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
if (new_euid != old_euid) if (new_euid != old_euid)
{ {
current->mm->dumpable=0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->fsuid = current->euid = new_euid; current->fsuid = current->euid = new_euid;
...@@ -703,7 +703,7 @@ asmlinkage long sys_setuid(uid_t uid) ...@@ -703,7 +703,7 @@ asmlinkage long sys_setuid(uid_t uid)
if (old_euid != uid) if (old_euid != uid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->fsuid = current->euid = uid; current->fsuid = current->euid = uid;
...@@ -748,7 +748,7 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid) ...@@ -748,7 +748,7 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
if (euid != (uid_t) -1) { if (euid != (uid_t) -1) {
if (euid != current->euid) if (euid != current->euid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->euid = euid; current->euid = euid;
...@@ -798,7 +798,7 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid) ...@@ -798,7 +798,7 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
if (egid != (gid_t) -1) { if (egid != (gid_t) -1) {
if (egid != current->egid) if (egid != current->egid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->egid = egid; current->egid = egid;
...@@ -845,7 +845,7 @@ asmlinkage long sys_setfsuid(uid_t uid) ...@@ -845,7 +845,7 @@ asmlinkage long sys_setfsuid(uid_t uid)
{ {
if (uid != old_fsuid) if (uid != old_fsuid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->fsuid = uid; current->fsuid = uid;
...@@ -875,7 +875,7 @@ asmlinkage long sys_setfsgid(gid_t gid) ...@@ -875,7 +875,7 @@ asmlinkage long sys_setfsgid(gid_t gid)
{ {
if (gid != old_fsgid) if (gid != old_fsgid)
{ {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
smp_wmb(); smp_wmb();
} }
current->fsgid = gid; current->fsgid = gid;
...@@ -1652,7 +1652,7 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, ...@@ -1652,7 +1652,7 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
error = 1; error = 1;
break; break;
case PR_SET_DUMPABLE: case PR_SET_DUMPABLE:
if (arg2 != 0 && arg2 != 1) { if (arg2 < 0 || arg2 > 2) {
error = -EINVAL; error = -EINVAL;
break; break;
} }
......
...@@ -58,6 +58,7 @@ extern int sysctl_overcommit_ratio; ...@@ -58,6 +58,7 @@ extern int sysctl_overcommit_ratio;
extern int max_threads; extern int max_threads;
extern int sysrq_enabled; extern int sysrq_enabled;
extern int core_uses_pid; extern int core_uses_pid;
extern int suid_dumpable;
extern char core_pattern[]; extern char core_pattern[];
extern int cad_pid; extern int cad_pid;
extern int pid_max; extern int pid_max;
...@@ -950,6 +951,14 @@ static ctl_table fs_table[] = { ...@@ -950,6 +951,14 @@ static ctl_table fs_table[] = {
.proc_handler = &proc_dointvec, .proc_handler = &proc_dointvec,
}, },
#endif #endif
{
.ctl_name = KERN_SETUID_DUMPABLE,
.procname = "suid_dumpable",
.data = &suid_dumpable,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{ .ctl_name = 0 } { .ctl_name = 0 }
}; };
......
...@@ -149,7 +149,7 @@ void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) ...@@ -149,7 +149,7 @@ void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
if (bprm->e_uid != current->uid || bprm->e_gid != current->gid || if (bprm->e_uid != current->uid || bprm->e_gid != current->gid ||
!cap_issubset (new_permitted, current->cap_permitted)) { !cap_issubset (new_permitted, current->cap_permitted)) {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) { if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
if (!capable(CAP_SETUID)) { if (!capable(CAP_SETUID)) {
......
...@@ -130,7 +130,7 @@ static void dummy_bprm_free_security (struct linux_binprm *bprm) ...@@ -130,7 +130,7 @@ static void dummy_bprm_free_security (struct linux_binprm *bprm)
static void dummy_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) static void dummy_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
{ {
if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) { if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) {
current->mm->dumpable = 0; current->mm->dumpable = suid_dumpable;
if ((unsafe & ~LSM_UNSAFE_PTRACE_CAP) && !capable(CAP_SETUID)) { if ((unsafe & ~LSM_UNSAFE_PTRACE_CAP) && !capable(CAP_SETUID)) {
bprm->e_uid = current->uid; bprm->e_uid = current->uid;
......
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