diff --git a/include/linux/sched.h b/include/linux/sched.h index fd4899236037..4b86cc029b00 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -719,6 +719,9 @@ struct task_struct { /* Unserialized, strictly 'current' */ + /* Save user-dumpable when mm goes away */ + unsigned user_dumpable:1; + /* Bit to tell LSMs we're in execve(): */ unsigned in_execve:1; unsigned in_iowait:1; diff --git a/kernel/exit.c b/kernel/exit.c index f7e72022301a..5d565733a73a 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -590,6 +590,7 @@ static void exit_mm(void) BUG_ON(mm != current->active_mm); /* more a memory barrier than a real lock */ task_lock(current); + current->user_dumpable = (get_dumpable(mm) == SUID_DUMP_USER); current->mm = NULL; up_read(&mm->mmap_sem); enter_lazy_tlb(mm, current); diff --git a/kernel/ptrace.c b/kernel/ptrace.c index bf8360e86f62..d89a81264414 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -281,11 +281,24 @@ static bool ptrace_has_cap(struct user_namespace *ns, unsigned int mode) return ns_capable(ns, CAP_SYS_PTRACE); } +static bool task_still_dumpable(struct task_struct *task, unsigned int mode) +{ + struct mm_struct *mm = task->mm; + if (mm) { + if (get_dumpable(mm) == SUID_DUMP_USER) + return true; + return ptrace_has_cap(mm->user_ns, mode); + } + + if (task->user_dumpable) + return true; + return ptrace_has_cap(&init_user_ns, mode); +} + /* Returns 0 on success, -errno on denial. */ static int __ptrace_may_access(struct task_struct *task, unsigned int mode) { const struct cred *cred = current_cred(), *tcred; - struct mm_struct *mm; kuid_t caller_uid; kgid_t caller_gid; @@ -346,11 +359,8 @@ ok: * Pairs with a write barrier in commit_creds(). */ smp_rmb(); - mm = task->mm; - if (mm && - ((get_dumpable(mm) != SUID_DUMP_USER) && - !ptrace_has_cap(mm->user_ns, mode))) - return -EPERM; + if (!task_still_dumpable(task, mode)) + return -EPERM; return security_ptrace_access_check(task, mode); }