shithub: riscv

Download patch

ref: 9aa6573359ffad41ef197b7f83623d7cbdeca068
parent: 5c95c50c6c470e1b9582998796555e00a6e0d7e5
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Fri Mar 25 22:37:42 EDT 2016

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.

--- a/sys/src/9/port/proc.c
+++ b/sys/src/9/port/proc.c
@@ -822,24 +822,37 @@
 	return up->trend == nil || up->tfn(arg);
 }
 
-void
+static void
 twakeup(Ureg*, Timer *t)
 {
 	Proc *p;
 	Rendez *trend;
 
+	ilock(t);
 	p = t->ta;
 	trend = p->trend;
-	p->trend = nil;
-	if(trend != nil)
+	if(trend != nil){
 		wakeup(trend);
+		p->trend = nil;
+	}
+	iunlock(t);
 }
 
+static void
+stoptimer(void)
+{
+	if(up->trend != nil){
+		up->trend = nil;
+		timerdel(up);
+	}
+}
+
 void
 tsleep(Rendez *r, int (*fn)(void*), void *arg, ulong ms)
 {
 	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);
 	}
 	up->tns = MS2NS(ms);
@@ -851,13 +864,11 @@
 	timeradd(up);
 
 	if(waserror()){
-		timerdel(up);
+		stoptimer();
 		nexterror();
 	}
 	sleep(r, tfn, arg);
-	if(up->tt != nil)
-		timerdel(up);
-	up->twhen = 0;
+	stoptimer();
 	poperror();
 }