Commit de399236 authored by Alexey Gladkov's avatar Alexey Gladkov Committed by Eric W. Biederman
Browse files

ucounts: Split rlimit and ucount values and max values



Since the semantics of maximum rlimit values are different, it would be
better not to mix ucount and rlimit values. This will prevent the error
of using inc_count/dec_ucount for rlimit parameters.

This patch also renames the functions to emphasize the lack of
connection between rlimit and ucount.

v3:
- Fix BUG:KASAN:use-after-free_in_dec_ucount.

v2:
- Fix the array-index-out-of-bounds that was found by the lkp project.

Reported-by: default avatarkernel test robot <oliver.sang@intel.com>
Signed-off-by: default avatarAlexey Gladkov <legion@kernel.org>
Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
Link: https://lkml.kernel.org/r/20220518171730.l65lmnnjtnxnftpq@example.org


Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
parent 31231092
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1880,7 +1880,7 @@ static int do_execveat_common(int fd, struct filename *filename,
	 * whether NPROC limit is still exceeded.
	 */
	if ((current->flags & PF_NPROC_EXCEEDED) &&
	    is_ucounts_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
	    is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
		retval = -EAGAIN;
		goto out_ret;
	}
+1 −1
Original line number Diff line number Diff line
@@ -276,7 +276,7 @@ static inline void task_sig(struct seq_file *m, struct task_struct *p)
		collect_sigign_sigcatch(p, &ignored, &caught);
		num_threads = get_nr_threads(p);
		rcu_read_lock();  /* FIXME: is this correct? */
		qsize = get_ucounts_value(task_ucounts(p), UCOUNT_RLIMIT_SIGPENDING);
		qsize = get_rlimit_value(task_ucounts(p), UCOUNT_RLIMIT_SIGPENDING);
		rcu_read_unlock();
		qlim = task_rlimit(p, RLIMIT_SIGPENDING);
		unlock_task_sighand(p, &flags);
+22 −13
Original line number Diff line number Diff line
@@ -54,15 +54,17 @@ enum ucount_type {
	UCOUNT_FANOTIFY_GROUPS,
	UCOUNT_FANOTIFY_MARKS,
#endif
	UCOUNT_COUNTS,
};

enum rlimit_type {
	UCOUNT_RLIMIT_NPROC,
	UCOUNT_RLIMIT_MSGQUEUE,
	UCOUNT_RLIMIT_SIGPENDING,
	UCOUNT_RLIMIT_MEMLOCK,
	UCOUNT_COUNTS,
	UCOUNT_RLIMIT_COUNTS,
};

#define MAX_PER_NAMESPACE_UCOUNTS UCOUNT_RLIMIT_NPROC

struct user_namespace {
	struct uid_gid_map	uid_map;
	struct uid_gid_map	gid_map;
@@ -99,6 +101,7 @@ struct user_namespace {
#endif
	struct ucounts		*ucounts;
	long ucount_max[UCOUNT_COUNTS];
	long rlimit_max[UCOUNT_RLIMIT_COUNTS];
} __randomize_layout;

struct ucounts {
@@ -107,6 +110,7 @@ struct ucounts {
	kuid_t uid;
	atomic_t count;
	atomic_long_t ucount[UCOUNT_COUNTS];
	atomic_long_t rlimit[UCOUNT_RLIMIT_COUNTS];
};

extern struct user_namespace init_user_ns;
@@ -120,21 +124,26 @@ struct ucounts *alloc_ucounts(struct user_namespace *ns, kuid_t uid);
struct ucounts * __must_check get_ucounts(struct ucounts *ucounts);
void put_ucounts(struct ucounts *ucounts);

static inline long get_ucounts_value(struct ucounts *ucounts, enum ucount_type type)
static inline long get_rlimit_value(struct ucounts *ucounts, enum rlimit_type type)
{
	return atomic_long_read(&ucounts->ucount[type]);
	return atomic_long_read(&ucounts->rlimit[type]);
}

long inc_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v);
bool dec_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v);
long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type);
void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum ucount_type type);
bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsigned long max);
long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
bool dec_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type);
void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum rlimit_type type);
bool is_rlimit_overlimit(struct ucounts *ucounts, enum rlimit_type type, unsigned long max);

static inline long get_userns_rlimit_max(struct user_namespace *ns, enum rlimit_type type)
{
	return READ_ONCE(ns->rlimit_max[type]);
}

static inline void set_rlimit_ucount_max(struct user_namespace *ns,
		enum ucount_type type, unsigned long max)
static inline void set_userns_rlimit_max(struct user_namespace *ns,
		enum rlimit_type type, unsigned long max)
{
	ns->ucount_max[type] = max <= LONG_MAX ? max : LONG_MAX;
	ns->rlimit_max[type] = max <= LONG_MAX ? max : LONG_MAX;
}

#ifdef CONFIG_USER_NS
+6 −6
Original line number Diff line number Diff line
@@ -926,13 +926,13 @@ void __init fork_init(void)
	init_task.signal->rlim[RLIMIT_SIGPENDING] =
		init_task.signal->rlim[RLIMIT_NPROC];

	for (i = 0; i < MAX_PER_NAMESPACE_UCOUNTS; i++)
	for (i = 0; i < UCOUNT_COUNTS; i++)
		init_user_ns.ucount_max[i] = max_threads/2;

	set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_NPROC,      RLIM_INFINITY);
	set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_MSGQUEUE,   RLIM_INFINITY);
	set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_SIGPENDING, RLIM_INFINITY);
	set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_MEMLOCK,    RLIM_INFINITY);
	set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_NPROC,      RLIM_INFINITY);
	set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MSGQUEUE,   RLIM_INFINITY);
	set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_SIGPENDING, RLIM_INFINITY);
	set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MEMLOCK,    RLIM_INFINITY);

#ifdef CONFIG_VMAP_STACK
	cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache",
@@ -2096,7 +2096,7 @@ static __latent_entropy struct task_struct *copy_process(
		goto bad_fork_free;

	retval = -EAGAIN;
	if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
	if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
		if (p->real_cred->user != INIT_USER &&
		    !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
			goto bad_fork_cleanup_count;
+1 −1
Original line number Diff line number Diff line
@@ -490,7 +490,7 @@ static void flag_nproc_exceeded(struct cred *new)
	 * for programs doing set*uid()+execve() by harmlessly deferring the
	 * failure to the execve() stage.
	 */
	if (is_ucounts_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
	if (is_rlimit_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
			new->user != INIT_USER)
		current->flags |= PF_NPROC_EXCEEDED;
	else
Loading