exec/ptrace: fix get_dumpable() incorrect tests
authorKees Cook <keescook@chromium.org>
Tue, 12 Nov 2013 23:11:17 +0000 (15:11 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 29 Nov 2013 19:11:44 +0000 (11:11 -0800)
commit d049f74f2dbe71354d43d393ac3a188947811348 upstream.

The get_dumpable() return value is not boolean.  Most users of the
function actually want to be testing for non-SUID_DUMP_USER(1) rather than
SUID_DUMP_DISABLE(0).  The SUID_DUMP_ROOT(2) is also considered a
protected state.  Almost all places did this correctly, excepting the two
places fixed in this patch.

Wrong logic:
    if (dumpable == SUID_DUMP_DISABLE) { /* be protective */ }
        or
    if (dumpable == 0) { /* be protective */ }
        or
    if (!dumpable) { /* be protective */ }

Correct logic:
    if (dumpable != SUID_DUMP_USER) { /* be protective */ }
        or
    if (dumpable != 1) { /* be protective */ }

Without this patch, if the system had set the sysctl fs/suid_dumpable=2, a
user was able to ptrace attach to processes that had dropped privileges to
that user.  (This may have been partially mitigated if Yama was enabled.)

The macros have been moved into the file that declares get/set_dumpable(),
which means things like the ia64 code can see them too.

CVE-2013-2929

Reported-by: Vasily Kulikov <segoon@openwall.com>
Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: "Luck, Tony" <tony.luck@intel.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/ia64/include/asm/processor.h
fs/exec.c
include/linux/binfmts.h
include/linux/sched.h
kernel/ptrace.c

index e0a899a1a8a665c140dba6255dbbde7528a16c79..5a84b3a5074158d8b0fe975d5def50689c8c735d 100644 (file)
@@ -319,7 +319,7 @@ struct thread_struct {
        regs->loadrs = 0;                                                                       \
        regs->r8 = get_dumpable(current->mm);   /* set "don't zap registers" flag */            \
        regs->r12 = new_sp - 16;        /* allocate 16 byte scratch area */                     \
-       if (unlikely(!get_dumpable(current->mm))) {                                                     \
+       if (unlikely(get_dumpable(current->mm) != SUID_DUMP_USER)) {    \
                /*                                                                              \
                 * Zap scratch regs to avoid leaking bits between processes with different      \
                 * uid/privileges.                                                              \
index 1f446705636bed7b7dd1816778c302572369b910..bb60cda5ee30765a171f373537212134d979f139 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1669,6 +1669,12 @@ int __get_dumpable(unsigned long mm_flags)
        return (ret > SUID_DUMP_USER) ? SUID_DUMP_ROOT : ret;
 }
 
+/*
+ * This returns the actual value of the suid_dumpable flag. For things
+ * that are using this for checking for privilege transitions, it must
+ * test against SUID_DUMP_USER rather than treating it as a boolean
+ * value.
+ */
 int get_dumpable(struct mm_struct *mm)
 {
        return __get_dumpable(mm->flags);
index 70cf138690e9184678b1dea1aa9a23dd1a10a487..df97ca4aae5200db352c0cbfdf75f9355f344288 100644 (file)
@@ -99,9 +99,6 @@ extern void setup_new_exec(struct linux_binprm * bprm);
 extern void would_dump(struct linux_binprm *, struct file *);
 
 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 */
 #define EXSTACK_DEFAULT   0    /* Whatever the arch defaults to */
index 7b64cd4e3e560ff4830aa12fc3a3f3f93fae5dd0..597c8ab005a013aa9d09f8260fc71f8d421181fb 100644 (file)
@@ -332,6 +332,10 @@ static inline void arch_pick_mmap_layout(struct mm_struct *mm) {}
 extern void set_dumpable(struct mm_struct *mm, int value);
 extern int get_dumpable(struct mm_struct *mm);
 
+#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 */
+
 /* mm flags */
 /* dumpable bits */
 #define MMF_DUMPABLE      0  /* core dump is permitted */
index 335a7ae697f5986269d94a445fd27142fc75c09f..afadcf7b4a22ddcdfd5b26993f892d8dd0eae653 100644 (file)
@@ -257,7 +257,8 @@ ok:
        if (task->mm)
                dumpable = get_dumpable(task->mm);
        rcu_read_lock();
-       if (!dumpable && !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
+       if (dumpable != SUID_DUMP_USER &&
+           !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
                rcu_read_unlock();
                return -EPERM;
        }