Skip to content

Commit 905ae01

Browse files
legionusebiederm
authored andcommitted
Add a reference to ucounts for each cred
For RLIMIT_NPROC and some other rlimits the user_struct that holds the global limit is kept alive for the lifetime of a process by keeping it in struct cred. Adding a pointer to ucounts in the struct cred will allow to track RLIMIT_NPROC not only for user in the system, but for user in the user_namespace. Updating ucounts may require memory allocation which may fail. So, we cannot change cred.ucounts in the commit_creds() because this function cannot fail and it should always return 0. For this reason, we modify cred.ucounts before calling the commit_creds(). Changelog v6: * Fix null-ptr-deref in is_ucounts_overlimit() detected by trinity. This error was caused by the fact that cred_alloc_blank() left the ucounts pointer empty. Reported-by: kernel test robot <[email protected]> Signed-off-by: Alexey Gladkov <[email protected]> Link: https://lkml.kernel.org/r/b37aaef28d8b9b0d757e07ba6dd27281bbe39259.1619094428.git.legion@kernel.org Signed-off-by: Eric W. Biederman <[email protected]>
1 parent f9c82a4 commit 905ae01

File tree

8 files changed

+108
-3
lines changed

8 files changed

+108
-3
lines changed

fs/exec.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,10 @@ int begin_new_exec(struct linux_binprm * bprm)
13601360
WRITE_ONCE(me->self_exec_id, me->self_exec_id + 1);
13611361
flush_signal_handlers(me, 0);
13621362

1363+
retval = set_cred_ucounts(bprm->cred);
1364+
if (retval < 0)
1365+
goto out_unlock;
1366+
13631367
/*
13641368
* install the new credentials for this executable
13651369
*/

include/linux/cred.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ struct cred {
144144
#endif
145145
struct user_struct *user; /* real user ID subscription */
146146
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
147+
struct ucounts *ucounts;
147148
struct group_info *group_info; /* supplementary groups for euid/fsgid */
148149
/* RCU deletion */
149150
union {
@@ -170,6 +171,7 @@ extern int set_security_override_from_ctx(struct cred *, const char *);
170171
extern int set_create_files_as(struct cred *, struct inode *);
171172
extern int cred_fscmp(const struct cred *, const struct cred *);
172173
extern void __init cred_init(void);
174+
extern int set_cred_ucounts(struct cred *);
173175

174176
/*
175177
* check for validity of credentials

include/linux/user_namespace.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,15 @@ struct ucounts {
100100
};
101101

102102
extern struct user_namespace init_user_ns;
103+
extern struct ucounts init_ucounts;
103104

104105
bool setup_userns_sysctls(struct user_namespace *ns);
105106
void retire_userns_sysctls(struct user_namespace *ns);
106107
struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid, enum ucount_type type);
107108
void dec_ucount(struct ucounts *ucounts, enum ucount_type type);
109+
struct ucounts *alloc_ucounts(struct user_namespace *ns, kuid_t uid);
110+
struct ucounts *get_ucounts(struct ucounts *ucounts);
111+
void put_ucounts(struct ucounts *ucounts);
108112

109113
#ifdef CONFIG_USER_NS
110114

kernel/cred.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ struct cred init_cred = {
6060
.user = INIT_USER,
6161
.user_ns = &init_user_ns,
6262
.group_info = &init_groups,
63+
.ucounts = &init_ucounts,
6364
};
6465

6566
static inline void set_cred_subscribers(struct cred *cred, int n)
@@ -119,6 +120,8 @@ static void put_cred_rcu(struct rcu_head *rcu)
119120
if (cred->group_info)
120121
put_group_info(cred->group_info);
121122
free_uid(cred->user);
123+
if (cred->ucounts)
124+
put_ucounts(cred->ucounts);
122125
put_user_ns(cred->user_ns);
123126
kmem_cache_free(cred_jar, cred);
124127
}
@@ -222,6 +225,7 @@ struct cred *cred_alloc_blank(void)
222225
#ifdef CONFIG_DEBUG_CREDENTIALS
223226
new->magic = CRED_MAGIC;
224227
#endif
228+
new->ucounts = get_ucounts(&init_ucounts);
225229

226230
if (security_cred_alloc_blank(new, GFP_KERNEL_ACCOUNT) < 0)
227231
goto error;
@@ -284,6 +288,11 @@ struct cred *prepare_creds(void)
284288

285289
if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
286290
goto error;
291+
292+
new->ucounts = get_ucounts(new->ucounts);
293+
if (!new->ucounts)
294+
goto error;
295+
287296
validate_creds(new);
288297
return new;
289298

@@ -363,6 +372,8 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)
363372
ret = create_user_ns(new);
364373
if (ret < 0)
365374
goto error_put;
375+
if (set_cred_ucounts(new) < 0)
376+
goto error_put;
366377
}
367378

368379
#ifdef CONFIG_KEYS
@@ -653,6 +664,31 @@ int cred_fscmp(const struct cred *a, const struct cred *b)
653664
}
654665
EXPORT_SYMBOL(cred_fscmp);
655666

667+
int set_cred_ucounts(struct cred *new)
668+
{
669+
struct task_struct *task = current;
670+
const struct cred *old = task->real_cred;
671+
struct ucounts *old_ucounts = new->ucounts;
672+
673+
if (new->user == old->user && new->user_ns == old->user_ns)
674+
return 0;
675+
676+
/*
677+
* This optimization is needed because alloc_ucounts() uses locks
678+
* for table lookups.
679+
*/
680+
if (old_ucounts && old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->euid))
681+
return 0;
682+
683+
if (!(new->ucounts = alloc_ucounts(new->user_ns, new->euid)))
684+
return -EAGAIN;
685+
686+
if (old_ucounts)
687+
put_ucounts(old_ucounts);
688+
689+
return 0;
690+
}
691+
656692
/*
657693
* initialise the credentials stuff
658694
*/
@@ -719,6 +755,10 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)
719755
if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
720756
goto error;
721757

758+
new->ucounts = get_ucounts(new->ucounts);
759+
if (!new->ucounts)
760+
goto error;
761+
722762
put_cred(old);
723763
validate_creds(new);
724764
return new;

kernel/fork.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,6 +2995,12 @@ int ksys_unshare(unsigned long unshare_flags)
29952995
if (err)
29962996
goto bad_unshare_cleanup_cred;
29972997

2998+
if (new_cred) {
2999+
err = set_cred_ucounts(new_cred);
3000+
if (err)
3001+
goto bad_unshare_cleanup_cred;
3002+
}
3003+
29983004
if (new_fs || new_fd || do_sysvsem || new_cred || new_nsproxy) {
29993005
if (do_sysvsem) {
30003006
/*

kernel/sys.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,10 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
552552
if (retval < 0)
553553
goto error;
554554

555+
retval = set_cred_ucounts(new);
556+
if (retval < 0)
557+
goto error;
558+
555559
return commit_creds(new);
556560

557561
error:
@@ -610,6 +614,10 @@ long __sys_setuid(uid_t uid)
610614
if (retval < 0)
611615
goto error;
612616

617+
retval = set_cred_ucounts(new);
618+
if (retval < 0)
619+
goto error;
620+
613621
return commit_creds(new);
614622

615623
error:
@@ -685,6 +693,10 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
685693
if (retval < 0)
686694
goto error;
687695

696+
retval = set_cred_ucounts(new);
697+
if (retval < 0)
698+
goto error;
699+
688700
return commit_creds(new);
689701

690702
error:

kernel/ucount.c

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
#include <linux/kmemleak.h>
99
#include <linux/user_namespace.h>
1010

11+
struct ucounts init_ucounts = {
12+
.ns = &init_user_ns,
13+
.uid = GLOBAL_ROOT_UID,
14+
.count = 1,
15+
};
16+
1117
#define UCOUNTS_HASHTABLE_BITS 10
1218
static struct hlist_head ucounts_hashtable[(1 << UCOUNTS_HASHTABLE_BITS)];
1319
static DEFINE_SPINLOCK(ucounts_lock);
@@ -125,7 +131,15 @@ static struct ucounts *find_ucounts(struct user_namespace *ns, kuid_t uid, struc
125131
return NULL;
126132
}
127133

128-
static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid)
134+
static void hlist_add_ucounts(struct ucounts *ucounts)
135+
{
136+
struct hlist_head *hashent = ucounts_hashentry(ucounts->ns, ucounts->uid);
137+
spin_lock_irq(&ucounts_lock);
138+
hlist_add_head(&ucounts->node, hashent);
139+
spin_unlock_irq(&ucounts_lock);
140+
}
141+
142+
struct ucounts *alloc_ucounts(struct user_namespace *ns, kuid_t uid)
129143
{
130144
struct hlist_head *hashent = ucounts_hashentry(ns, uid);
131145
struct ucounts *ucounts, *new;
@@ -160,7 +174,26 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid)
160174
return ucounts;
161175
}
162176

163-
static void put_ucounts(struct ucounts *ucounts)
177+
struct ucounts *get_ucounts(struct ucounts *ucounts)
178+
{
179+
unsigned long flags;
180+
181+
if (!ucounts)
182+
return NULL;
183+
184+
spin_lock_irqsave(&ucounts_lock, flags);
185+
if (ucounts->count == INT_MAX) {
186+
WARN_ONCE(1, "ucounts: counter has reached its maximum value");
187+
ucounts = NULL;
188+
} else {
189+
ucounts->count += 1;
190+
}
191+
spin_unlock_irqrestore(&ucounts_lock, flags);
192+
193+
return ucounts;
194+
}
195+
196+
void put_ucounts(struct ucounts *ucounts)
164197
{
165198
unsigned long flags;
166199

@@ -194,7 +227,7 @@ struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid,
194227
{
195228
struct ucounts *ucounts, *iter, *bad;
196229
struct user_namespace *tns;
197-
ucounts = get_ucounts(ns, uid);
230+
ucounts = alloc_ucounts(ns, uid);
198231
for (iter = ucounts; iter; iter = tns->ucounts) {
199232
long max;
200233
tns = iter->ns;
@@ -237,6 +270,7 @@ static __init int user_namespace_sysctl_init(void)
237270
BUG_ON(!user_header);
238271
BUG_ON(!setup_userns_sysctls(&init_user_ns));
239272
#endif
273+
hlist_add_ucounts(&init_ucounts);
240274
return 0;
241275
}
242276
subsys_initcall(user_namespace_sysctl_init);

kernel/user_namespace.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,9 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
13401340
put_user_ns(cred->user_ns);
13411341
set_cred_user_ns(cred, get_user_ns(user_ns));
13421342

1343+
if (set_cred_ucounts(cred) < 0)
1344+
return -EINVAL;
1345+
13431346
return 0;
13441347
}
13451348

0 commit comments

Comments
 (0)