Skip to content

Commit 2d3409e

Browse files
committed
Merge branch 'ucount-rlimit-fixes-for-v5.17' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace
Pull ucounts fixes from Eric Biederman: "Michal Koutný recently found some bugs in the enforcement of RLIMIT_NPROC in the recent ucount rlimit implementation. In this set of patches I have developed a very conservative approach changing only what is necessary to fix the bugs that I can see clearly. Cleanups and anything that is making the code more consistent can follow after we have the code working as it has historically. The problem is not so much inconsistencies (although those exist) but that it is very difficult to figure out what the code should be doing in the case of RLIMIT_NPROC. All other rlimits are only enforced where the resource is acquired (allocated). RLIMIT_NPROC by necessity needs to be enforced in an additional location, and our current implementation stumbled it's way into that implementation" * 'ucount-rlimit-fixes-for-v5.17' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: ucounts: Handle wrapping in is_ucounts_overlimit ucounts: Move RLIMIT_NPROC handling after set_user ucounts: Base set_cred_ucounts changes on the real user ucounts: Enforce RLIMIT_NPROC not RLIMIT_NPROC+1 rlimit: Fix RLIMIT_NPROC enforcement failure caused by capability calls in set_user
2 parents 4f12b74 + 0cbae9e commit 2d3409e

4 files changed

Lines changed: 23 additions & 19 deletions

File tree

kernel/cred.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -665,21 +665,16 @@ EXPORT_SYMBOL(cred_fscmp);
665665

666666
int set_cred_ucounts(struct cred *new)
667667
{
668-
struct task_struct *task = current;
669-
const struct cred *old = task->real_cred;
670668
struct ucounts *new_ucounts, *old_ucounts = new->ucounts;
671669

672-
if (new->user == old->user && new->user_ns == old->user_ns)
673-
return 0;
674-
675670
/*
676671
* This optimization is needed because alloc_ucounts() uses locks
677672
* for table lookups.
678673
*/
679-
if (old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->euid))
674+
if (old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->uid))
680675
return 0;
681676

682-
if (!(new_ucounts = alloc_ucounts(new->user_ns, new->euid)))
677+
if (!(new_ucounts = alloc_ucounts(new->user_ns, new->uid)))
683678
return -EAGAIN;
684679

685680
new->ucounts = new_ucounts;

kernel/fork.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,18 +2021,18 @@ static __latent_entropy struct task_struct *copy_process(
20212021
#ifdef CONFIG_PROVE_LOCKING
20222022
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
20232023
#endif
2024+
retval = copy_creds(p, clone_flags);
2025+
if (retval < 0)
2026+
goto bad_fork_free;
2027+
20242028
retval = -EAGAIN;
20252029
if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
20262030
if (p->real_cred->user != INIT_USER &&
20272031
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
2028-
goto bad_fork_free;
2032+
goto bad_fork_cleanup_count;
20292033
}
20302034
current->flags &= ~PF_NPROC_EXCEEDED;
20312035

2032-
retval = copy_creds(p, clone_flags);
2033-
if (retval < 0)
2034-
goto bad_fork_free;
2035-
20362036
/*
20372037
* If multiple threads are within copy_process(), then this check
20382038
* triggers too late. This doesn't hurt, the check is only there

kernel/sys.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,16 @@ static int set_user(struct cred *new)
472472
if (!new_user)
473473
return -EAGAIN;
474474

475+
free_uid(new->user);
476+
new->user = new_user;
477+
return 0;
478+
}
479+
480+
static void flag_nproc_exceeded(struct cred *new)
481+
{
482+
if (new->ucounts == current_ucounts())
483+
return;
484+
475485
/*
476486
* We don't fail in case of NPROC limit excess here because too many
477487
* poorly written programs don't check set*uid() return code, assuming
@@ -480,15 +490,10 @@ static int set_user(struct cred *new)
480490
* failure to the execve() stage.
481491
*/
482492
if (is_ucounts_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
483-
new_user != INIT_USER &&
484-
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
493+
new->user != INIT_USER)
485494
current->flags |= PF_NPROC_EXCEEDED;
486495
else
487496
current->flags &= ~PF_NPROC_EXCEEDED;
488-
489-
free_uid(new->user);
490-
new->user = new_user;
491-
return 0;
492497
}
493498

494499
/*
@@ -563,6 +568,7 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
563568
if (retval < 0)
564569
goto error;
565570

571+
flag_nproc_exceeded(new);
566572
return commit_creds(new);
567573

568574
error:
@@ -625,6 +631,7 @@ long __sys_setuid(uid_t uid)
625631
if (retval < 0)
626632
goto error;
627633

634+
flag_nproc_exceeded(new);
628635
return commit_creds(new);
629636

630637
error:
@@ -704,6 +711,7 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
704711
if (retval < 0)
705712
goto error;
706713

714+
flag_nproc_exceeded(new);
707715
return commit_creds(new);
708716

709717
error:

kernel/ucount.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsign
350350
if (rlimit > LONG_MAX)
351351
max = LONG_MAX;
352352
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
353-
if (get_ucounts_value(iter, type) > max)
353+
long val = get_ucounts_value(iter, type);
354+
if (val < 0 || val > max)
354355
return true;
355356
max = READ_ONCE(iter->ns->ucount_max[type]);
356357
}

0 commit comments

Comments
 (0)