shithub: riscv

Download patch

ref: 656dd953a85b043c1eb085d5d8e1f7f7f1e4452f
parent: 85e144dcb0c621161ca4275f7613d606da088ca0
author: cinap_lenrek <[email protected]>
date: Mon Apr 13 19:18:56 EDT 2015

segment: fix read/write g->dlen race, avoid copying kernel memory, qlock

code like "return g->dlen;" is wrong as we do not hold the
qlock of the global segment. another process could come in
and override g->dlen making us return the wrong byte count.

avoid copying when we already got a kernel address (kernel memory
is the same on processes) which is the case with bread()/bwrite().
this is the same optimization that devsd does.

also avoid allocating/freeing and copying while holding the qlock.
when we copy to/from user memory, we might fault preventing
others from accessing the segment while fault handling is in
progress.

--- a/sys/src/9/port/devsegment.c
+++ b/sys/src/9/port/devsegment.c
@@ -231,7 +231,7 @@
 		if(g->s == nil)
 			error("segment not yet allocated");
 		if(g->kproc == nil){
-			qlock(&g->l);
+			eqlock(&g->l);
 			if(waserror()){
 				qunlock(&g->l);
 				nexterror();
@@ -316,6 +316,57 @@
 }
 
 static long
+segmentio(Globalseg *g, void *a, long n, vlong off, int wr)
+{
+	uintptr m;
+	void *b;
+
+	m = g->s->top - g->s->base;
+	if(off < 0 || off >= m){
+		if(wr)
+			error(Ebadarg);
+		return 0;
+	}
+	if(off+n > m){
+		if(wr)
+			error(Ebadarg);	
+		n = m - off;
+	}
+
+	if((uintptr)a > KZERO)
+		b = a;
+	else {
+		b = smalloc(n);
+		if(waserror()){
+			free(b);
+			nexterror();
+		}
+		if(wr)
+			memmove(b, a, n);
+	}
+
+	eqlock(&g->l);
+	if(waserror()){
+		qunlock(&g->l);
+		nexterror();
+	}
+	g->off = off + g->s->base;
+	g->data = b;
+	g->dlen = n;
+	docmd(g, wr ? Cwrite : Cread);
+	qunlock(&g->l);
+	poperror();
+
+	if(a != b){
+		if(!wr)
+			memmove(a, b, n);
+		free(b);
+		poperror();
+	}
+	return n;
+}
+
+static long
 segmentread(Chan *c, void *a, long n, vlong voff)
 {
 	Globalseg *g;
@@ -324,9 +375,9 @@
 	if(c->qid.type == QTDIR)
 		return devdirread(c, a, n, (Dirtab *)0, 0L, segmentgen);
 
+	g = c->aux;
 	switch(TYPE(c)){
 	case Qctl:
-		g = c->aux;
 		if(g->s == nil)
 			error("segment not yet allocated");
 		if((g->s->type&SG_TYPE) == SG_FIXED)
@@ -336,26 +387,7 @@
 			snprint(buf, sizeof(buf), "va %#p %#p\n", g->s->base, g->s->top-g->s->base);
 		return readstr(voff, a, n, buf);
 	case Qdata:
-		g = c->aux;
-		if(voff > g->s->top - g->s->base)
-			error(Ebadarg);
-		if(voff + n > g->s->top - g->s->base)
-			n = g->s->top - g->s->base - voff;
-		qlock(&g->l);
-		g->off = voff + g->s->base;
-		g->data = smalloc(n);
-		if(waserror()){
-			free(g->data);
-			qunlock(&g->l);
-			nexterror();
-		}
-		g->dlen = n;
-		docmd(g, Cread);
-		memmove(a, g->data, g->dlen);
-		free(g->data);
-		qunlock(&g->l);
-		poperror();
-		return g->dlen;
+		return segmentio(g, a, n, voff, 0);
 	default:
 		panic("segmentread");
 	}
@@ -372,9 +404,9 @@
 	if(c->qid.type == QTDIR)
 		error(Eperm);
 
+	g = c->aux;
 	switch(TYPE(c)){
 	case Qctl:
-		g = c->aux;
 		cb = parsecmd(a, n);
 		if(strcmp(cb->f[0], "va") == 0){
 			if(g->s != nil)
@@ -398,24 +430,7 @@
 			error(Ebadctl);
 		break;
 	case Qdata:
-		g = c->aux;
-		if(voff + n > g->s->top - g->s->base)
-			error(Ebadarg);
-		qlock(&g->l);
-		g->off = voff + g->s->base;
-		g->data = smalloc(n);
-		if(waserror()){
-			free(g->data);
-			qunlock(&g->l);
-			nexterror();
-		}
-		g->dlen = n;
-		memmove(g->data, a, g->dlen);
-		docmd(g, Cwrite);
-		free(g->data);
-		qunlock(&g->l);
-		poperror();
-		return g->dlen;
+		return segmentio(g, a, n, voff, 1);
 	default:
 		panic("segmentwrite");
 	}