fix races in the uidinfo subsystem, several problems existed:

1) while allocating a uidinfo struct malloc is called with M_WAITOK,
   it's possible that while asleep another process by the same user
   could have woken up earlier and inserted an entry into the uid
   hash table.  Having redundant entries causes inconsistancies that
   we can't handle.

   fix: do a non-waiting malloc, and if that fails then do a blocking
   malloc, after waking up check that no one else has inserted an entry
   for us already.

2) Because many checks for sbsize were done as "test then set" in a non
   atomic manner it was possible to exceed the limits put up via races.

   fix: instead of querying the count then setting, we just attempt to
   set the count and leave it up to the function to return success or
   failure.

3) The uidinfo code was inlining and repeating, lookups and insertions
   and deletions needed to be in their own functions for clarity.

Reviewed by: green
This commit is contained in:
alfred 2000-06-22 22:27:16 +00:00
parent 8b986caf33
commit 7f71a1a091
12 changed files with 119 additions and 81 deletions

View File

@ -1260,7 +1260,7 @@ loop:
/*
* Decrement the count of procs running with this uid.
*/
(void)chgproccnt(q->p_cred->p_ruid, -1);
(void)chgproccnt(q->p_cred->p_ruid, -1, 0);
/*
* Free up credentials.

View File

@ -387,7 +387,7 @@ proc0_init(dummy)
/*
* Charge root for one process.
*/
(void)chgproccnt(0, 1);
(void)chgproccnt(0, 1, 0);
/*
* Initialize the current process pointer (curproc) before

View File

@ -472,7 +472,7 @@ loop:
/*
* Decrement the count of procs running with this uid.
*/
(void)chgproccnt(p->p_cred->p_ruid, -1);
(void)chgproccnt(p->p_cred->p_ruid, -1, 0);
/*
* Release reference to text vnode

View File

@ -183,7 +183,7 @@ fork1(p1, flags, procp)
struct proc *p2, *pptr;
uid_t uid;
struct proc *newproc;
int count;
int ok;
static int pidchecked = 0;
struct forklist *ep;
@ -245,9 +245,8 @@ fork1(p1, flags, procp)
* Increment the count of procs running with this uid. Don't allow
* a nonprivileged user to exceed their current limit.
*/
count = chgproccnt(uid, 1);
if (uid != 0 && count > p1->p_rlimit[RLIMIT_NPROC].rlim_cur) {
(void)chgproccnt(uid, -1);
ok = chgproccnt(uid, 1, p1->p_rlimit[RLIMIT_NPROC].rlim_cur);
if (uid != 0 && !ok) {
/*
* Back out the process count
*/

View File

@ -72,6 +72,10 @@ static u_long uihash; /* size of hash table - 1 */
static void orphanpg __P((struct pgrp *pg));
static struct uidinfo *uifind(uid_t uid);
static struct uidinfo *uicreate(uid_t uid);
static int uifree(struct uidinfo *uip);
/*
* Other process lists
*/
@ -99,85 +103,119 @@ procinit()
}
/*
* Change the count associated with number of processes
* a given user is using.
* find/create a uidinfo struct for the uid passed in
*/
int
chgproccnt(uid, diff)
uid_t uid;
int diff;
static struct uidinfo *
uifind(uid)
uid_t uid;
{
register struct uidinfo *uip;
register struct uihashhead *uipp;
struct uihashhead *uipp;
struct uidinfo *uip;
uipp = UIHASH(uid);
LIST_FOREACH(uip, uipp, ui_hash)
if (uip->ui_uid == uid)
break;
if (uip) {
uip->ui_proccnt += diff;
if (uip->ui_proccnt < 0)
panic("chgproccnt: procs < 0");
if (uip->ui_proccnt > 0 || uip->ui_sbsize > 0)
return (uip->ui_proccnt);
return (uip);
}
static struct uidinfo *
uicreate(uid)
uid_t uid;
{
struct uidinfo *uip, *norace;
MALLOC(uip, struct uidinfo *, sizeof(*uip), M_PROC, M_NOWAIT);
if (uip == NULL) {
MALLOC(uip, struct uidinfo *, sizeof(*uip), M_PROC, M_WAITOK);
/*
* if we M_WAITOK we must look afterwards or risk
* redundant entries
*/
norace = uifind(uid);
if (norace != NULL) {
FREE(uip, M_PROC);
return (norace);
}
}
LIST_INSERT_HEAD(UIHASH(uid), uip, ui_hash);
uip->ui_uid = uid;
uip->ui_proccnt = 0;
uip->ui_sbsize = 0;
return (uip);
}
static int
uifree(uip)
struct uidinfo *uip;
{
if (uip->ui_sbsize == 0 && uip->ui_proccnt == 0) {
LIST_REMOVE(uip, ui_hash);
FREE(uip, M_PROC);
return (1);
}
return (0);
}
/*
* Change the count associated with number of processes
* a given user is using. When 'max' is 0, don't enforce a limit
*/
int
chgproccnt(uid, diff, max)
uid_t uid;
int diff;
int max;
{
struct uidinfo *uip;
uip = uifind(uid);
if (diff < 0)
KASSERT(uip != NULL, ("reducing proccnt: lost count, uid = %d", uid));
if (uip == NULL)
uip = uicreate(uid);
/* don't allow them to exceed max, but allow subtraction */
if (diff > 0 && uip->ui_proccnt + diff > max && max != 0) {
(void)uifree(uip);
return (0);
}
if (diff <= 0) {
if (diff == 0)
return(0);
panic("chgproccnt: lost user");
}
MALLOC(uip, struct uidinfo *, sizeof(*uip), M_PROC, M_WAITOK);
LIST_INSERT_HEAD(uipp, uip, ui_hash);
uip->ui_uid = uid;
uip->ui_proccnt = diff;
uip->ui_sbsize = 0;
return (diff);
uip->ui_proccnt += diff;
(void)uifree(uip);
return (1);
}
/*
* Change the total socket buffer size a user has used.
*/
rlim_t
chgsbsize(uid, diff)
int
chgsbsize(uid, diff, max)
uid_t uid;
rlim_t diff;
rlim_t max;
{
register struct uidinfo *uip;
register struct uihashhead *uipp;
struct uidinfo *uip;
uipp = UIHASH(uid);
LIST_FOREACH(uip, uipp, ui_hash)
if (uip->ui_uid == uid)
break;
if (diff <= 0) {
if (diff == 0)
return (uip ? uip->ui_sbsize : 0);
KASSERT(uip != NULL, ("uidinfo (%d) gone", uid));
uip = uifind(uid);
if (diff < 0)
KASSERT(uip != NULL, ("reducing sbsize: lost count, uid = %d", uid));
if (uip == NULL)
uip = uicreate(uid);
/* don't allow them to exceed max, but allow subtraction */
if (diff > 0 && uip->ui_sbsize + diff > max) {
(void)uifree(uip);
return (0);
}
if (uip) {
uip->ui_sbsize += diff;
if (uip->ui_sbsize == 0 && uip->ui_proccnt == 0) {
LIST_REMOVE(uip, ui_hash);
FREE(uip, M_PROC);
return (0);
}
return (uip->ui_sbsize);
}
MALLOC(uip, struct uidinfo *, sizeof(*uip), M_PROC, M_WAITOK);
LIST_INSERT_HEAD(uipp, uip, ui_hash);
uip->ui_uid = uid;
uip->ui_proccnt = 0;
uip->ui_sbsize = diff;
return (diff);
uip->ui_sbsize += diff;
(void)uifree(uip);
return (1);
}
/*
* Is p an inferior of the current process?
*/
int
inT
inferior(p)
register struct proc *p;
{

View File

@ -430,8 +430,8 @@ setuid(p, uap)
* Transfer proc count to new user.
*/
if (uid != pc->p_ruid) {
(void)chgproccnt(pc->p_ruid, -1);
(void)chgproccnt(uid, 1);
(void)chgproccnt(pc->p_ruid, -1, 0);
(void)chgproccnt(uid, 1, 0);
}
/*
* Set real uid
@ -679,8 +679,8 @@ setreuid(p, uap)
setsugid(p);
}
if (ruid != (uid_t)-1 && pc->p_ruid != ruid) {
(void)chgproccnt(pc->p_ruid, -1);
(void)chgproccnt(ruid, 1);
(void)chgproccnt(pc->p_ruid, -1, 0);
(void)chgproccnt(ruid, 1, 0);
pc->p_ruid = ruid;
setsugid(p);
}
@ -772,8 +772,8 @@ setresuid(p, uap)
setsugid(p);
}
if (ruid != (uid_t)-1 && pc->p_ruid != ruid) {
(void)chgproccnt(pc->p_ruid, -1);
(void)chgproccnt(ruid, 1);
(void)chgproccnt(pc->p_ruid, -1, 0);
(void)chgproccnt(ruid, 1, 0);
pc->p_ruid = ruid;
setsugid(p);
}

View File

@ -432,10 +432,10 @@ sbreserve(sb, cc, so, p)
if ((u_quad_t)cc > (u_quad_t)sb_max * MCLBYTES / (MSIZE + MCLBYTES))
return (0);
delta = (rlim_t)cc - sb->sb_hiwat;
if (p && delta >= 0 && chgsbsize(so->so_cred->cr_uid, 0) + delta >
p->p_rlimit[RLIMIT_SBSIZE].rlim_cur)
if (p && !chgsbsize(so->so_cred->cr_uid, delta,
p->p_rlimit[RLIMIT_SBSIZE].rlim_cur)) {
return (0);
(void)chgsbsize(so->so_cred->cr_uid, delta);
}
sb->sb_hiwat = cc;
sb->sb_mbmax = min(cc * sb_efficiency, sb_max);
if (sb->sb_lowat > sb->sb_hiwat)
@ -453,7 +453,7 @@ sbrelease(sb, so)
{
sbflush(sb);
(void)chgsbsize(so->so_cred->cr_uid, -(rlim_t)sb->sb_hiwat);
(void)chgsbsize(so->so_cred->cr_uid, -(rlim_t)sb->sb_hiwat, RLIM_INFINITY);
sb->sb_hiwat = sb->sb_mbmax = 0;
}

View File

@ -191,10 +191,10 @@ sodealloc(so)
so->so_gencnt = ++so_gencnt;
if (so->so_rcv.sb_hiwat)
(void)chgsbsize(so->so_cred->cr_uid,
-(rlim_t)so->so_rcv.sb_hiwat);
-(rlim_t)so->so_rcv.sb_hiwat, RLIM_INFINITY);
if (so->so_snd.sb_hiwat)
(void)chgsbsize(so->so_cred->cr_uid,
-(rlim_t)so->so_snd.sb_hiwat);
-(rlim_t)so->so_snd.sb_hiwat, RLIM_INFINITY);
if (so->so_accf != NULL) {
if (so->so_accf->so_accept_filter != NULL &&
so->so_accf->so_accept_filter->accf_destroy != NULL) {

View File

@ -432,10 +432,10 @@ sbreserve(sb, cc, so, p)
if ((u_quad_t)cc > (u_quad_t)sb_max * MCLBYTES / (MSIZE + MCLBYTES))
return (0);
delta = (rlim_t)cc - sb->sb_hiwat;
if (p && delta >= 0 && chgsbsize(so->so_cred->cr_uid, 0) + delta >
p->p_rlimit[RLIMIT_SBSIZE].rlim_cur)
if (p && !chgsbsize(so->so_cred->cr_uid, delta,
p->p_rlimit[RLIMIT_SBSIZE].rlim_cur)) {
return (0);
(void)chgsbsize(so->so_cred->cr_uid, delta);
}
sb->sb_hiwat = cc;
sb->sb_mbmax = min(cc * sb_efficiency, sb_max);
if (sb->sb_lowat > sb->sb_hiwat)
@ -453,7 +453,7 @@ sbrelease(sb, so)
{
sbflush(sb);
(void)chgsbsize(so->so_cred->cr_uid, -(rlim_t)sb->sb_hiwat);
(void)chgsbsize(so->so_cred->cr_uid, -(rlim_t)sb->sb_hiwat, RLIM_INFINITY);
sb->sb_hiwat = sb->sb_mbmax = 0;
}

View File

@ -48,6 +48,7 @@
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/resourcevar.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/un.h>
@ -236,7 +237,7 @@ uipc_rcvd(struct socket *so, int flags)
unp->unp_mbcnt = so->so_rcv.sb_mbcnt;
so2->so_snd.sb_hiwat += unp->unp_cc - so->so_rcv.sb_cc;
(void)chgsbsize(so2->so_cred->cr_uid,
(rlim_t)unp->unp_cc - so->so_rcv.sb_cc);
(rlim_t)unp->unp_cc - so->so_rcv.sb_cc, RLIM_INFINITY);
unp->unp_cc = so->so_rcv.sb_cc;
sowwakeup(so2);
break;
@ -344,7 +345,7 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam,
so->so_snd.sb_hiwat -=
so2->so_rcv.sb_cc - unp->unp_conn->unp_cc;
(void)chgsbsize(so->so_cred->cr_uid,
(rlim_t)unp->unp_conn->unp_cc - so2->so_rcv.sb_cc);
(rlim_t)unp->unp_conn->unp_cc - so2->so_rcv.sb_cc, RLIM_INFINITY);
unp->unp_conn->unp_cc = so2->so_rcv.sb_cc;
sorwakeup(so2);
m = 0;

View File

@ -1260,7 +1260,7 @@ loop:
/*
* Decrement the count of procs running with this uid.
*/
(void)chgproccnt(q->p_cred->p_ruid, -1);
(void)chgproccnt(q->p_cred->p_ruid, -1, 0);
/*
* Free up credentials.

View File

@ -420,8 +420,8 @@ struct pgrp *pgfind __P((pid_t)); /* Find process group by id. */
struct vm_zone;
extern struct vm_zone *proc_zone;
int chgproccnt __P((uid_t uid, int diff));
rlim_t chgsbsize __P((uid_t uid, rlim_t diff));
int chgproccnt __P((uid_t uid, int diff, int max));
int chgsbsize __P((uid_t uid, rlim_t diff, rlim_t max));
int enterpgrp __P((struct proc *p, pid_t pgid, int mksess));
void fixjobc __P((struct proc *p, struct pgrp *pgrp, int entering));
int inferior __P((struct proc *p));