History

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

Linus Torvalds committed 4 years ago

Commit 2d3409e2 parents 4f12b74 0cbae9e4 files · +23 −19Browse files

Showing the changes to kernel (4 of 4 files). Show all

Unified · Split

4 files
  1. kernel/cred.c +2 −7
  2. kernel/fork.c +5 −5
  3. kernel/sys.c +14 −6
  4. kernel/ucount.c +2 −1
kernel/cred.c+2−7
@@ -665,21 +665,16 @@ EXPORT_SYMBOL(cred_fscmp); · 664 unmodified lines
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+5−5
@@ -2021,18 +2021,18 @@ static __latent_entropy struct task_struct *copy_process( · 2020 unmodified lines
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+14−6
@@ -472,6 +472,16 @@ static int set_user(struct cred *new) · 471 unmodified lines
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) · 2 unmodified lines
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) · 68 unmodified lines
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) · 56 unmodified lines
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) · 73 unmodified lines
704711 if (retval < 0)
705712 goto error;
706713
714+ flag_nproc_exceeded(new);
707715 return commit_creds(new);
708716
709717 error:
kernel/ucount.c+2−1
@@ -350,7 +350,8 @@ bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsign · 349 unmodified lines
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 }