[PATCH 8/8] ucounts: Use the same code to enforce RLIMIT_NPROC in fork and exec

From: Eric W. Biederman
Date: Thu Feb 10 2022 - 21:14:40 EST


Historically these pieces of code have slightly diverged and caused
problems. To avoid that in the future create a common function to see
if RLIMIT_NPROC is over a limit and the limit should be enforced.

Signed-off-by: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
---
fs/exec.c | 7 ++-----
include/linux/sched/signal.h | 2 ++
kernel/fork.c | 25 +++++++++++++++++++------
3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 01c8c7bae9b4..8913dbb9a479 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1875,14 +1875,11 @@ static int do_execveat_common(int fd, struct filename *filename,
return PTR_ERR(filename);

/*
- * After calling set*uid() is RLIMT_NPROC exceeded?
+ * After calling set*uid() is RLIMIT_NPROC exceeded?
* This can not be checked in set*uid() because too many programs don't
* check the setuid() return code.
*/
- if ((current->flags & PF_NPROC_CHECK) &&
- is_ucounts_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
- (current_ucounts() != &init_ucounts) &&
- !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) {
+ if ((current->flags & PF_NPROC_CHECK) && is_nproc_overlimit()) {
retval = -EAGAIN;
goto out_ret;
}
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index b6ecb9fc4cd2..b682131c52fa 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -742,4 +742,6 @@ static inline unsigned long rlimit_max(unsigned int limit)
return task_rlimit_max(current, limit);
}

+extern bool is_nproc_overlimit(void);
+
#endif /* _LINUX_SCHED_SIGNAL_H */
diff --git a/kernel/fork.c b/kernel/fork.c
index 79661678a5bf..a18f15053e22 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1891,6 +1891,23 @@ static void copy_oom_score_adj(u64 clone_flags, struct task_struct *tsk)
mutex_unlock(&oom_adj_mutex);
}

+static bool is_task_nproc_overlimit(struct task_struct *task)
+{
+ struct ucounts *ucounts = task_ucounts(task);
+ /* clone does not change RLIMIT_NPROC. The parents value is safe. */
+ unsigned long limit = rlimit(RLIMIT_NPROC);
+
+ return is_ucounts_overlimit(ucounts, UCOUNT_RLIMIT_NPROC, limit) &&
+ (ucounts != &init_ucounts) &&
+ !has_capability(task, CAP_SYS_RESOURCE) &&
+ !has_capability(task, CAP_SYS_ADMIN);
+}
+
+bool is_nproc_overlimit(void)
+{
+ return is_task_nproc_overlimit(current);
+}
+
/*
* This creates a new process as a copy of the old one,
* but does not actually start it yet.
@@ -2028,12 +2045,8 @@ static __latent_entropy struct task_struct *copy_process(
retval = -EAGAIN;
if (inc_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1) == LONG_MAX)
goto bad_fork_cleanup_count;
- if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
- if ((task_ucounts(p) != &init_ucounts) &&
- !has_capability(p, CAP_SYS_RESOURCE) &&
- !has_capability(p, CAP_SYS_ADMIN))
- goto bad_fork_cleanup_count;
- }
+ if (is_task_nproc_overlimit(p))
+ goto bad_fork_cleanup_count;
current->flags &= ~PF_NPROC_CHECK;

/*
--
2.29.2