Commit 2d3409eb authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'ucount-rlimit-fixes-for-v5.17' of...

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
parents 4f12b742 0cbae9e2
...@@ -665,21 +665,16 @@ EXPORT_SYMBOL(cred_fscmp); ...@@ -665,21 +665,16 @@ EXPORT_SYMBOL(cred_fscmp);
int set_cred_ucounts(struct cred *new) int set_cred_ucounts(struct cred *new)
{ {
struct task_struct *task = current;
const struct cred *old = task->real_cred;
struct ucounts *new_ucounts, *old_ucounts = new->ucounts; struct ucounts *new_ucounts, *old_ucounts = new->ucounts;
if (new->user == old->user && new->user_ns == old->user_ns)
return 0;
/* /*
* This optimization is needed because alloc_ucounts() uses locks * This optimization is needed because alloc_ucounts() uses locks
* for table lookups. * for table lookups.
*/ */
if (old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->euid)) if (old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->uid))
return 0; return 0;
if (!(new_ucounts = alloc_ucounts(new->user_ns, new->euid))) if (!(new_ucounts = alloc_ucounts(new->user_ns, new->uid)))
return -EAGAIN; return -EAGAIN;
new->ucounts = new_ucounts; new->ucounts = new_ucounts;
......
...@@ -2021,18 +2021,18 @@ static __latent_entropy struct task_struct *copy_process( ...@@ -2021,18 +2021,18 @@ static __latent_entropy struct task_struct *copy_process(
#ifdef CONFIG_PROVE_LOCKING #ifdef CONFIG_PROVE_LOCKING
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled); DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif #endif
retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;
retval = -EAGAIN; retval = -EAGAIN;
if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) { if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
if (p->real_cred->user != INIT_USER && if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free; goto bad_fork_cleanup_count;
} }
current->flags &= ~PF_NPROC_EXCEEDED; current->flags &= ~PF_NPROC_EXCEEDED;
retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;
/* /*
* If multiple threads are within copy_process(), then this check * If multiple threads are within copy_process(), then this check
* triggers too late. This doesn't hurt, the check is only there * triggers too late. This doesn't hurt, the check is only there
......
...@@ -472,6 +472,16 @@ static int set_user(struct cred *new) ...@@ -472,6 +472,16 @@ static int set_user(struct cred *new)
if (!new_user) if (!new_user)
return -EAGAIN; return -EAGAIN;
free_uid(new->user);
new->user = new_user;
return 0;
}
static void flag_nproc_exceeded(struct cred *new)
{
if (new->ucounts == current_ucounts())
return;
/* /*
* We don't fail in case of NPROC limit excess here because too many * We don't fail in case of NPROC limit excess here because too many
* poorly written programs don't check set*uid() return code, assuming * poorly written programs don't check set*uid() return code, assuming
...@@ -480,15 +490,10 @@ static int set_user(struct cred *new) ...@@ -480,15 +490,10 @@ static int set_user(struct cred *new)
* failure to the execve() stage. * failure to the execve() stage.
*/ */
if (is_ucounts_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) && if (is_ucounts_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
new_user != INIT_USER && new->user != INIT_USER)
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
current->flags |= PF_NPROC_EXCEEDED; current->flags |= PF_NPROC_EXCEEDED;
else else
current->flags &= ~PF_NPROC_EXCEEDED; current->flags &= ~PF_NPROC_EXCEEDED;
free_uid(new->user);
new->user = new_user;
return 0;
} }
/* /*
...@@ -563,6 +568,7 @@ long __sys_setreuid(uid_t ruid, uid_t euid) ...@@ -563,6 +568,7 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
if (retval < 0) if (retval < 0)
goto error; goto error;
flag_nproc_exceeded(new);
return commit_creds(new); return commit_creds(new);
error: error:
...@@ -625,6 +631,7 @@ long __sys_setuid(uid_t uid) ...@@ -625,6 +631,7 @@ long __sys_setuid(uid_t uid)
if (retval < 0) if (retval < 0)
goto error; goto error;
flag_nproc_exceeded(new);
return commit_creds(new); return commit_creds(new);
error: error:
...@@ -704,6 +711,7 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid) ...@@ -704,6 +711,7 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
if (retval < 0) if (retval < 0)
goto error; goto error;
flag_nproc_exceeded(new);
return commit_creds(new); return commit_creds(new);
error: error:
......
...@@ -350,7 +350,8 @@ bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsign ...@@ -350,7 +350,8 @@ bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsign
if (rlimit > LONG_MAX) if (rlimit > LONG_MAX)
max = LONG_MAX; max = LONG_MAX;
for (iter = ucounts; iter; iter = iter->ns->ucounts) { for (iter = ucounts; iter; iter = iter->ns->ucounts) {
if (get_ucounts_value(iter, type) > max) long val = get_ucounts_value(iter, type);
if (val < 0 || val > max)
return true; return true;
max = READ_ONCE(iter->ns->ucount_max[type]); max = READ_ONCE(iter->ns->ucount_max[type]);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment