Better refcount locking to avoid use after free

Under some conditions it could be happen that on a freeing capi object
a other thread still get a reference and then it use the already freed object.
To avoid this, we do 2 things, we only take a refernce if get_obj() succeed, it
do not longer succeed if the object is already cleaned. We also force scheduling
after releaseing the lock before really freeing the object - so the waiting thread
will not get a new reference.
This commit is contained in:
Karsten Keil 2014-10-14 14:06:28 +02:00
parent d9a97bb810
commit fc757c58dc
7 changed files with 119 additions and 35 deletions

View File

@ -103,8 +103,12 @@ int register_lController(struct mApplication *appl, struct lController *lc)
eprint("%s: controller idx %d ID:%d\already registered\n", CAPIobjIDstr(&appl->cobj), i, lc->cobj.id);
return -EBUSY;
}
appl->lcl[i] = lc;
get_cobj(&lc->cobj);
if (get_cobj(&lc->cobj)) {
appl->lcl[i] = lc;
} else {
eprint("%s: controller idx %d cannot get controller object %s\n", CAPIobjIDstr(&appl->cobj), i, CAPIobjIDstr(&lc->cobj));
return -EINVAL;
}
return 0;
}
@ -290,8 +294,12 @@ struct lController *get_lController(struct mApplication *appl, unsigned int cont
wprint("%s: wrong controller id %d (max %d)\n", CAPIobjIDstr(&appl->cobj), cont, mI_ControllerCount);
lc = NULL;
}
if (lc)
get_cobj(&lc->cobj);
if (lc) {
if (!get_cobj(&lc->cobj)) {
wprint("%s: cannot get controller object %s\n", CAPIobjIDstr(&appl->cobj), CAPIobjIDstr(&lc->cobj));
lc = NULL;
}
}
return lc;
}

View File

@ -132,6 +132,7 @@ void CAPIobj_exit(void)
while (co) {
cn = co->nextD;
eprint("%s: uid=%i refcnt %d in dangling list - freeing now\n", CAPIobjIDstr(co), co->uid, co->refcnt);
co->cleaned = 1;
cobj_free(co);
co = cn;
}
@ -357,9 +358,12 @@ struct mCAPIobj *__get_cobj(struct mCAPIobj *co, const char *file, int lineno)
struct mCAPIobj *get_cobj(struct mCAPIobj *co)
#endif
{
struct mCAPIobj *p;
if (co) {
if (co->parent) {
pthread_rwlock_wrlock(&co->parent->lock);
p = co->parent;
if (p) {
pthread_rwlock_wrlock(&p->lock);
} else {
if (co->type != Cot_Root) { /* has no parent */
cobj_err("%s: parent not assigned\n", CAPIobjIDstr(co));
@ -367,12 +371,17 @@ struct mCAPIobj *get_cobj(struct mCAPIobj *co)
}
pthread_mutex_lock(&rootLock);
}
if (co->freeing)
cobj_err("Currently freeing %s refcnt: %d\n", CAPIobjIDstr(co), co->refcnt);
co->refcnt++;
coref_dbg("%s\n", CAPIobjIDstr(co));
if (co->parent)
pthread_rwlock_unlock(&co->parent->lock);
if (co->cleaned) {
cobj_warn("%s: pending free detected - do not get object\n", CAPIobjIDstr(co));
co = NULL;
} else {
if (co->freeing)
cobj_err("Currently freeing %s refcnt: %d\n", CAPIobjIDstr(co), co->refcnt);
co->refcnt++;
coref_dbg("%s\n", CAPIobjIDstr(co));
}
if (p)
pthread_rwlock_unlock(&p->lock);
else
pthread_mutex_unlock(&rootLock);
} else
@ -403,6 +412,10 @@ int put_cobj(struct mCAPIobj *co)
ret = co->refcnt;
if (co->cleaned && co->refcnt <= 0) { /* last ref */
pthread_rwlock_unlock(&p->lock);
/* OK not perfect but scheduling here should prevent us from access of freed memory, if a other thread
still pending on the lock - a cleaned object is not longer listed and do not return success in get_obj()
so getting a new reference should not happen after this point */
sched_yield();
cobj_free(co);
} else {
pthread_rwlock_unlock(&p->lock);
@ -447,8 +460,13 @@ struct mCAPIobj *get_next_cobj(struct mCAPIobj *parent, struct mCAPIobj *cur)
else
next = parent->listhead;
if (next) {
next->refcnt++;
coref_dbg("%s: next %s\n", CAPIobjIDstr(cur), CAPIobjIDstr(next));
if (next->cleaned) {
cobj_warn("%s: pending free detected - do not get next\n", CAPIobjIDstr(next));
next = NULL;
} else {
next->refcnt++;
coref_dbg("%s: next %s\n", CAPIobjIDstr(cur), CAPIobjIDstr(next));
}
}
pthread_rwlock_unlock(&parent->lock);
if (parent->freeing)

View File

@ -1141,6 +1141,16 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
return ret;
}
if (get_cobj(&lp->cobj)) {
bi->lp = lp;
} else {
bi->lp = NULL;
ret = -EINVAL;
wprint("Cannot get logical controller object\n");
close(sk);
return ret;
}
switch (lp->btype) {
case BType_Direct:
bi->from_down = recvBdirect;
@ -1160,6 +1170,8 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
eprint("Error unnkown BType %d\n", lp->btype);
dprint(MIDEBUG_CONTROLLER, "close socket bi[%d]->fd %d\n", bi->nr, sk);
close(sk);
bi->lp = NULL;
put_cobj(&lp->cobj);
return -EINVAL;
}
bi->type = lp->btype;
@ -1181,11 +1193,11 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
bi->from_down = dummy_btrans;
bi->from_up = dummy_btrans;
bi->type = BType_None;
bi->lp = NULL;
put_cobj(&lp->cobj);
} else {
bi->closed = 0;
bi->fd = sk;
bi->lp = lp;
get_cobj(&lp->cobj);
bi->org_rx_min = MISDN_CTRL_RX_SIZE_IGNORE;
bi->rx_min = MISDN_CTRL_RX_SIZE_IGNORE;
bi->org_rx_max = MISDN_CTRL_RX_SIZE_IGNORE;
@ -1243,8 +1255,8 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
dprint(MIDEBUG_CONTROLLER, "close socket bi[%d]->fd %d\n", bi->nr, sk);
close(sk);
bi->fd = -1;
put_cobj(&lp->cobj);
bi->lp = NULL;
put_cobj(&lp->cobj);
} else {
dprint(MIDEBUG_CONTROLLER, "Controller%d: Bchannel %d socket %d added to poll idx %d\n",
bi->pc->profile.ncontroller, bi->nr, sk, ret);
@ -1260,8 +1272,8 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
dprint(MIDEBUG_CONTROLLER, "close socket bi[%d]->fd %d\n", bi->nr, sk);
close(sk);
bi->fd = -1;
put_cobj(&lp->cobj);
bi->lp = NULL;
put_cobj(&lp->cobj);
} else {
ret = 0;
bi->UpId = 0;
@ -1274,8 +1286,8 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
dprint(MIDEBUG_CONTROLLER, "close socket bi[%d]->fd %d\n", bi->nr, sk);
close(sk);
bi->fd = -1;
put_cobj(&lp->cobj);
bi->lp = NULL;
put_cobj(&lp->cobj);
return ret;
} else {
bi->pfd[2].fd = bi->tty;
@ -1287,8 +1299,8 @@ int OpenBInstance(struct BInstance *bi, struct lPLCI *lp)
dprint(MIDEBUG_CONTROLLER, "close socket bi[%d]->fd %d\n", bi->nr, sk);
close(sk);
bi->fd = -1;
put_cobj(&lp->cobj);
bi->lp = NULL;
put_cobj(&lp->cobj);
} else {
ret = 0;
bi->UpId = 0;

View File

@ -128,14 +128,19 @@ struct lController *addlController(struct mApplication *app, struct pController
lc = calloc(1, sizeof(*lc));
if (lc) {
lc->cobj.id2 = app->cobj.id2;
if (!get_cobj(&app->cobj)) {
eprint("Cannot get application object\n");
free(lc);
return NULL;
}
ret = init_cobj_registered(&lc->cobj, &pc->cobjLC, Cot_lController, 0x0000ff);
if (ret) {
eprint("Controller%d: Application %d - cannot init\n", pc->profile.ncontroller, app->cobj.id2);
put_cobj(&app->cobj);
free(lc);
lc = NULL;
} else {
lc->Appl = app;
get_cobj(&app->cobj);
lc->listen_m.fsm = &listen_fsm;
lc->listen_m.state = ST_LISTEN_L_0;
lc->listen_m.debug = MIDEBUG_CONTROLLER & mI_debug_mask;
@ -146,10 +151,13 @@ struct lController *addlController(struct mApplication *app, struct pController
lc->CIPmask2 = 0;
ret = register_lController(app, lc);
if (ret) {
lc->Appl = NULL;
put_cobj(&app->cobj);
eprint("Controller%d: - cannot register LC on Application %d - %s\n", pc->profile.ncontroller,
app->cobj.id2, strerror(-ret));
free(lc);
lc->cobj.cleaned = 1;
delist_cobj(&lc->cobj);
put_cobj(&lc->cobj);
lc = NULL;
}
}

View File

@ -1747,10 +1747,24 @@ int lPLCICreate(struct lPLCI **lpp, struct lController *lc, struct mPLCI *plci,
return ret;
}
lp->cipmask = cipmask;
lp->lc = lc;
get_cobj(&lc->cobj);
lp->Appl = lc->Appl;
get_cobj(&lc->Appl->cobj);
if (get_cobj(&lc->cobj)) {
lp->lc = lc;
} else {
wprint("%s: Cannot get lController object\n", CAPIobjIDstr(&lc->cobj));
lp->cobj.cleaned = 1;
delist_cobj(&lp->cobj);
put_cobj(&lp->cobj); /* will cleanup and free too */
return -EINVAL;
}
if (get_cobj(&lc->Appl->cobj)) {
lp->Appl = lc->Appl;
} else {
wprint("%s: Cannot get application object\n", CAPIobjIDstr(&lc->Appl->cobj));
lp->cobj.cleaned = 1;
delist_cobj(&lp->cobj);
put_cobj(&lp->cobj); /* will cleanup and free too */
return -EINVAL;
}
if (plci->pc->profile.goptions & 0x0008) {
/* DTMF */
lp->l1dtmf = 1;
@ -1762,8 +1776,15 @@ int lPLCICreate(struct lPLCI **lpp, struct lController *lc, struct mPLCI *plci,
lp->plci_m.printdebug = lPLCI_debug;
lp->chid.nr = MI_CHAN_NONE;
lp->autohangup = 1;
init_timer(&lp->atimer, mICAPItimer_base, lp, atimer_timeout);
get_cobj(&lp->cobj); /* timer ref */
if (get_cobj(&lp->cobj)) { /* timer ref */
init_timer(&lp->atimer, mICAPItimer_base, lp, atimer_timeout);
} else {
wprint("%s: Cannot get lplci object for timer ref\n", CAPIobjIDstr(&lp->cobj));
lp->cobj.cleaned = 1;
delist_cobj(&lp->cobj);
put_cobj(&lp->cobj); /* will cleanup and free too */
return -EINVAL;
}
*lpp = lp;
return 0;
}

View File

@ -133,19 +133,26 @@ struct mCAPIobj {
#endif
};
#if __GNUC_PREREQ (3,4)
# define __WUR __attribute__ ((__warn_unused_result__))
#else
# define __WUR
#endif
#ifdef MISDN_CAPI_REFCOUNT_DEBUG
struct mCAPIobj *__get_cobj(struct mCAPIobj *, const char *, int);
struct mCAPIobj *__get_cobj(struct mCAPIobj *, const char *, int) __WUR;
int __put_cobj(struct mCAPIobj *, const char *, int);
struct mCAPIobj *__get_next_cobj(struct mCAPIobj *, struct mCAPIobj *, const char *, int);
struct mCAPIobj *__get_next_cobj(struct mCAPIobj *, struct mCAPIobj *, const char *, int) __WUR;
int __delist_cobj(struct mCAPIobj *, const char *, int);
#define get_cobj(co) __get_cobj(co, __FILE__, __LINE__)
#define put_cobj(co) __put_cobj(co, __FILE__, __LINE__)
#define get_next_cobj(pa, co) __get_next_cobj(pa, co, __FILE__, __LINE__)
#define delist_cobj(co) __delist_cobj(co, __FILE__, __LINE__)
#else
struct mCAPIobj *get_cobj(struct mCAPIobj *);
struct mCAPIobj *get_cobj(struct mCAPIobj *) __WUR;
int put_cobj(struct mCAPIobj *);
struct mCAPIobj *get_next_cobj(struct mCAPIobj *, struct mCAPIobj *);
struct mCAPIobj *get_next_cobj(struct mCAPIobj *, struct mCAPIobj *) __WUR;
int delist_cobj(struct mCAPIobj *);
#endif

View File

@ -640,6 +640,7 @@ struct mNCCI *ncciCreate(struct lPLCI *lp)
{
struct mNCCI *nc;
int err;
struct mApplication *appl = NULL;
nc = calloc(1, sizeof(*nc));
if (!nc) {
@ -647,6 +648,15 @@ struct mNCCI *ncciCreate(struct lPLCI *lp)
return NULL;
}
nc->cobj.id2 = lp->cobj.id2;
if (lp->Appl) {
if (get_cobj(&lp->Appl->cobj)) {
appl = lp->Appl;
} else {
wprint("Cannot get application object\n");
free(nc);
return NULL;
}
}
err = init_cobj_registered(&nc->cobj, &lp->cobj, Cot_NCCI, 0x01ffff);
if (!err) {
dprint(MIDEBUG_NCCI, "NCCI%06x: will be created now\n", nc->cobj.id);
@ -654,9 +664,7 @@ struct mNCCI *ncciCreate(struct lPLCI *lp)
nc->ncci_m.debug = MIDEBUG_NCCI & mI_debug_mask;
nc->ncci_m.userdata = nc;
nc->ncci_m.printdebug = ncci_debug;
nc->appl = lp->Appl;
if (nc->appl)
get_cobj(&nc->appl->cobj);
nc->appl = appl;
nc->BIlink = lp->BIlink;
nc->window = lp->Appl->MaxB3Blk;
pthread_mutex_init(&nc->lock, NULL);
@ -710,6 +718,8 @@ struct mNCCI *ncciCreate(struct lPLCI *lp)
nc->osize = 256;
dprint(MIDEBUG_NCCI, "%s: created\n", CAPIobjIDstr(&nc->cobj));
} else {
if (appl)
put_cobj(&appl->cobj);
free(nc);
nc = NULL;
}