coredump: disable core dumps when transitionning via a suidexec

From: Willy Tarreau
Date: Sun Dec 26 2021 - 06:41:15 EST


This adds an MMF_NOT_DUMPABLE flag to mm->flags that is set when
transitionning via a suidexec program, and is only reset when the target
program changes the RLIMIT_CORE values or changes its dumpable state
using prctl(PR_SET_DUMPABLE).

This allows programs like su/sudo to start a program without this
program risking to dump a core, but it also lets such programs
explicitly disable this protection by simply changing their core
limits.
---
fs/coredump.c | 2 ++
fs/exec.c | 4 +++-
include/linux/sched/coredump.h | 4 +++-
kernel/sys.c | 12 ++++++++++++
4 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index 3224dee44d30..5f0bfe2c00a6 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -609,6 +609,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
goto fail;
if (!__get_dumpable(cprm.mm_flags))
goto fail;
+ if (cprm.mm_flags & MMF_NOT_DUMPABLE_MASK)
+ goto fail;

cred = prepare_creds();
if (!cred)
diff --git a/fs/exec.c b/fs/exec.c
index ac7b51b51f38..42f74b6f0ca0 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1348,8 +1348,10 @@ int begin_new_exec(struct linux_binprm * bprm)
*/
if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP ||
!(uid_eq(current_euid(), current_uid()) &&
- gid_eq(current_egid(), current_gid())))
+ gid_eq(current_egid(), current_gid()))) {
+ set_bit(MMF_NOT_DUMPABLE, &current->mm->flags);
set_dumpable(current->mm, suid_dumpable);
+ }
else
set_dumpable(current->mm, SUID_DUMP_USER);

diff --git a/include/linux/sched/coredump.h b/include/linux/sched/coredump.h
index 4d9e3a656875..e307f70c5b95 100644
--- a/include/linux/sched/coredump.h
+++ b/include/linux/sched/coredump.h
@@ -81,9 +81,11 @@ static inline int get_dumpable(struct mm_struct *mm)
* lifecycle of this mm, just for simplicity.
*/
#define MMF_HAS_PINNED 28 /* FOLL_PIN has run, never cleared */
+#define MMF_NOT_DUMPABLE 29 /* dump disabled by suidexec */
#define MMF_DISABLE_THP_MASK (1 << MMF_DISABLE_THP)
+#define MMF_NOT_DUMPABLE_MASK (1 << MMF_NOT_DUMPABLE)

#define MMF_INIT_MASK (MMF_DUMPABLE_MASK | MMF_DUMP_FILTER_MASK |\
- MMF_DISABLE_THP_MASK)
+ MMF_DISABLE_THP_MASK | MMF_NOT_DUMPABLE_MASK)

#endif /* _LINUX_SCHED_COREDUMP_H */
diff --git a/kernel/sys.c b/kernel/sys.c
index 8fdac0d90504..c5bb0247bde4 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1610,6 +1610,17 @@ int do_prlimit(struct task_struct *tsk, unsigned int resource,
new_rlim->rlim_cur != RLIM_INFINITY &&
IS_ENABLED(CONFIG_POSIX_TIMERS))
update_rlimit_cpu(tsk, new_rlim->rlim_cur);
+
+ /*
+ * If an application wants to change its own core dump settings, it
+ * wants to decide on its dumpability so we reset MMF_NOT_DUMPABLE.
+ * We only do that for applications that were previously dumpable,
+ * so that suid programs such as su/sudo setting the target process'
+ * limits do no disable the protection by accident.
+ */
+ if (!retval && new_rlim && resource == RLIMIT_CORE && tsk == current &&
+ get_dumpable(tsk->mm))
+ clear_bit(MMF_NOT_DUMPABLE, &tsk->mm->flags);
out:
read_unlock(&tasklist_lock);
return retval;
@@ -2293,6 +2304,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
break;
}
set_dumpable(me->mm, arg2);
+ clear_bit(MMF_NOT_DUMPABLE, &me->mm->flags);
break;

case PR_SET_UNALIGN:
--
2.17.5


--qMm9M+Fa2AknHoGS
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="dumpable.c"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/prctl.h>
#include <sys/resource.h>

// ./dumpable => returns current dumpable
// ./dumpable [0|1|2] <cmd> [args...] => executes cmd under dumpable state
// and with RLIMIT_CPU=0
int main(int argc, char **argv, char **envp)
{
struct rlimit lim;

if (argc < 2) {
printf("dumpable=%d\n", prctl(PR_GET_DUMPABLE, 0, 0, 0, 0));
return 0;
}

prctl(PR_SET_DUMPABLE, atoi(argv[1]), 0, 0, 0);

lim.rlim_cur = RLIM_INFINITY;
lim.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &lim);

lim.rlim_cur = 0;
lim.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_CPU, &lim);

return execve(argv[2], argv + 2, envp);
}

--qMm9M+Fa2AknHoGS--