kernel: fix tsleep()/twakeup()/tsemacquire() race

tsleep() used to cancel the timer with:

if(up->tt != nil)
	timerdel(up);

which still can result in twakeup() to fire after tsleep()
returns (because we set Timer.tt to nil *before* we call the tfn).
in most cases, this is not an issue as the Rendez*
usually is just &up->sleep, but when it is dynamically allocated
or on the stack like in tsemacquire(), twakeup() will call
wakeup() on a potentially garbage Rendez structure!

to fix the race, we execute the wakup() with the Timer lock
held, and set p->trend to nil only after we called wakeup().

that way, the timerdel(); which unconditionally locks the Timer;
can act as a proper barrier and use up->trend == nil as the
condition if the timer has already fired.
This commit is contained in:
cinap_lenrek 2016-03-26 02:37:42 +01:00
parent 5c95c50c6c
commit 9aa6573359

View file

@ -822,24 +822,37 @@ tfn(void *arg)
return up->trend == nil || up->tfn(arg); return up->trend == nil || up->tfn(arg);
} }
void static void
twakeup(Ureg*, Timer *t) twakeup(Ureg*, Timer *t)
{ {
Proc *p; Proc *p;
Rendez *trend; Rendez *trend;
ilock(t);
p = t->ta; p = t->ta;
trend = p->trend; trend = p->trend;
p->trend = nil; if(trend != nil){
if(trend != nil)
wakeup(trend); wakeup(trend);
p->trend = nil;
}
iunlock(t);
}
static void
stoptimer(void)
{
if(up->trend != nil){
up->trend = nil;
timerdel(up);
}
} }
void void
tsleep(Rendez *r, int (*fn)(void*), void *arg, ulong ms) tsleep(Rendez *r, int (*fn)(void*), void *arg, ulong ms)
{ {
if(up->tt != nil){ if(up->tt != nil){
print("tsleep: timer active: mode %d, tf %#p\n", up->tmode, up->tf); print("%s %lux: tsleep timer active: mode %d, tf %#p, pc %#p\n",
up->text, up->pid, up->tmode, up->tf, getcallerpc(&r));
timerdel(up); timerdel(up);
} }
up->tns = MS2NS(ms); up->tns = MS2NS(ms);
@ -851,13 +864,11 @@ tsleep(Rendez *r, int (*fn)(void*), void *arg, ulong ms)
timeradd(up); timeradd(up);
if(waserror()){ if(waserror()){
timerdel(up); stoptimer();
nexterror(); nexterror();
} }
sleep(r, tfn, arg); sleep(r, tfn, arg);
if(up->tt != nil) stoptimer();
timerdel(up);
up->twhen = 0;
poperror(); poperror();
} }