Reimplement RLIMIT_SIGPENDING on top of ucounts

The rlimit counter is tied to uid in the user_namespace. This allows
rlimit values to be specified in userns even if they are already
globally exceeded by the user. However, the value of the previous
user_namespaces cannot be exceeded.

Changelog

v11:
* Revert most of changes to fix performance issues.

v10:
* Fix memory leak on get_ucounts failure.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
Link: https://lkml.kernel.org/r/df9d7764dddd50f28616b7840de74ec0f81711a8.1619094428.git.legion@kernel.org
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
This commit is contained in:
Alexey Gladkov 2021-04-22 14:27:13 +02:00 committed by Eric W. Biederman
parent 6e52a9f053
commit d646969055
9 changed files with 21 additions and 16 deletions

View File

@ -284,7 +284,7 @@ static inline void task_sig(struct seq_file *m, struct task_struct *p)
collect_sigign_sigcatch(p, &ignored, &caught); collect_sigign_sigcatch(p, &ignored, &caught);
num_threads = get_nr_threads(p); num_threads = get_nr_threads(p);
rcu_read_lock(); /* FIXME: is this correct? */ rcu_read_lock(); /* FIXME: is this correct? */
qsize = atomic_read(&__task_cred(p)->user->sigpending); qsize = get_ucounts_value(task_ucounts(p), UCOUNT_RLIMIT_SIGPENDING);
rcu_read_unlock(); rcu_read_unlock();
qlim = task_rlimit(p, RLIMIT_SIGPENDING); qlim = task_rlimit(p, RLIMIT_SIGPENDING);
unlock_task_sighand(p, &flags); unlock_task_sighand(p, &flags);

View File

@ -12,7 +12,6 @@
*/ */
struct user_struct { struct user_struct {
refcount_t __count; /* reference count */ refcount_t __count; /* reference count */
atomic_t sigpending; /* How many pending signals does this user have? */
#ifdef CONFIG_FANOTIFY #ifdef CONFIG_FANOTIFY
atomic_t fanotify_listeners; atomic_t fanotify_listeners;
#endif #endif

View File

@ -13,6 +13,8 @@ typedef struct kernel_siginfo {
__SIGINFO; __SIGINFO;
} kernel_siginfo_t; } kernel_siginfo_t;
struct ucounts;
/* /*
* Real Time signals may be queued. * Real Time signals may be queued.
*/ */
@ -21,7 +23,7 @@ struct sigqueue {
struct list_head list; struct list_head list;
int flags; int flags;
kernel_siginfo_t info; kernel_siginfo_t info;
struct user_struct *user; struct ucounts *ucounts;
}; };
/* flags values. */ /* flags values. */

View File

@ -52,6 +52,7 @@ enum ucount_type {
#endif #endif
UCOUNT_RLIMIT_NPROC, UCOUNT_RLIMIT_NPROC,
UCOUNT_RLIMIT_MSGQUEUE, UCOUNT_RLIMIT_MSGQUEUE,
UCOUNT_RLIMIT_SIGPENDING,
UCOUNT_COUNTS, UCOUNT_COUNTS,
}; };

View File

@ -824,6 +824,7 @@ void __init fork_init(void)
init_user_ns.ucount_max[UCOUNT_RLIMIT_NPROC] = task_rlimit(&init_task, RLIMIT_NPROC); init_user_ns.ucount_max[UCOUNT_RLIMIT_NPROC] = task_rlimit(&init_task, RLIMIT_NPROC);
init_user_ns.ucount_max[UCOUNT_RLIMIT_MSGQUEUE] = task_rlimit(&init_task, RLIMIT_MSGQUEUE); init_user_ns.ucount_max[UCOUNT_RLIMIT_MSGQUEUE] = task_rlimit(&init_task, RLIMIT_MSGQUEUE);
init_user_ns.ucount_max[UCOUNT_RLIMIT_SIGPENDING] = task_rlimit(&init_task, RLIMIT_SIGPENDING);
#ifdef CONFIG_VMAP_STACK #ifdef CONFIG_VMAP_STACK
cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache", cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache",

View File

@ -413,8 +413,8 @@ static struct sigqueue *
__sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimit) __sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimit)
{ {
struct sigqueue *q = NULL; struct sigqueue *q = NULL;
struct user_struct *user; struct ucounts *ucounts = NULL;
int sigpending; long sigpending;
/* /*
* Protect access to @t credentials. This can go away when all * Protect access to @t credentials. This can go away when all
@ -425,27 +425,26 @@ __sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimi
* changes from/to zero. * changes from/to zero.
*/ */
rcu_read_lock(); rcu_read_lock();
user = __task_cred(t)->user; ucounts = task_ucounts(t);
sigpending = atomic_inc_return(&user->sigpending); sigpending = inc_rlimit_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING, 1);
if (sigpending == 1) if (sigpending == 1)
get_uid(user); ucounts = get_ucounts(ucounts);
rcu_read_unlock(); rcu_read_unlock();
if (override_rlimit || likely(sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) { if (override_rlimit || (sigpending < LONG_MAX && sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) {
q = kmem_cache_alloc(sigqueue_cachep, flags); q = kmem_cache_alloc(sigqueue_cachep, flags);
} else { } else {
print_dropped_signal(sig); print_dropped_signal(sig);
} }
if (unlikely(q == NULL)) { if (unlikely(q == NULL)) {
if (atomic_dec_and_test(&user->sigpending)) if (ucounts && dec_rlimit_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING, 1))
free_uid(user); put_ucounts(ucounts);
} else { } else {
INIT_LIST_HEAD(&q->list); INIT_LIST_HEAD(&q->list);
q->flags = 0; q->flags = 0;
q->user = user; q->ucounts = ucounts;
} }
return q; return q;
} }
@ -453,8 +452,10 @@ static void __sigqueue_free(struct sigqueue *q)
{ {
if (q->flags & SIGQUEUE_PREALLOC) if (q->flags & SIGQUEUE_PREALLOC)
return; return;
if (atomic_dec_and_test(&q->user->sigpending)) if (q->ucounts && dec_rlimit_ucounts(q->ucounts, UCOUNT_RLIMIT_SIGPENDING, 1)) {
free_uid(q->user); put_ucounts(q->ucounts);
q->ucounts = NULL;
}
kmem_cache_free(sigqueue_cachep, q); kmem_cache_free(sigqueue_cachep, q);
} }

View File

@ -80,6 +80,7 @@ static struct ctl_table user_table[] = {
UCOUNT_ENTRY("max_inotify_instances"), UCOUNT_ENTRY("max_inotify_instances"),
UCOUNT_ENTRY("max_inotify_watches"), UCOUNT_ENTRY("max_inotify_watches"),
#endif #endif
{ },
{ }, { },
{ }, { },
{ } { }

View File

@ -98,7 +98,6 @@ static DEFINE_SPINLOCK(uidhash_lock);
/* root_user.__count is 1, for init task cred */ /* root_user.__count is 1, for init task cred */
struct user_struct root_user = { struct user_struct root_user = {
.__count = REFCOUNT_INIT(1), .__count = REFCOUNT_INIT(1),
.sigpending = ATOMIC_INIT(0),
.locked_shm = 0, .locked_shm = 0,
.uid = GLOBAL_ROOT_UID, .uid = GLOBAL_ROOT_UID,
.ratelimit = RATELIMIT_STATE_INIT(root_user.ratelimit, 0, 0), .ratelimit = RATELIMIT_STATE_INIT(root_user.ratelimit, 0, 0),

View File

@ -124,6 +124,7 @@ int create_user_ns(struct cred *new)
} }
ns->ucount_max[UCOUNT_RLIMIT_NPROC] = rlimit(RLIMIT_NPROC); ns->ucount_max[UCOUNT_RLIMIT_NPROC] = rlimit(RLIMIT_NPROC);
ns->ucount_max[UCOUNT_RLIMIT_MSGQUEUE] = rlimit(RLIMIT_MSGQUEUE); ns->ucount_max[UCOUNT_RLIMIT_MSGQUEUE] = rlimit(RLIMIT_MSGQUEUE);
ns->ucount_max[UCOUNT_RLIMIT_SIGPENDING] = rlimit(RLIMIT_SIGPENDING);
ns->ucounts = ucounts; ns->ucounts = ucounts;
/* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */