shithub: riscv

Download patch

ref: e9bb7876e1bd7f9aaef4e1cd0c64c97f411ffde5
parent: 3351b674802d5a69ddb6d4d0354c0e034804b0c1
author: Jacob Moody <moody@posixcafe.org>
date: Fri May 27 22:23:29 EDT 2022

kernel: add chdev command to devcons

--- a/sys/man/3/cons
+++ b/sys/man/3/cons
@@ -90,10 +90,60 @@
 .PP
 The
 .B drivers
-file contains, one per line, a listing of the drivers configured in the kernel, in the format
+file contains, one per line, a listing of available kernel drivers, in the format
 .IP
 .EX
 #c cons
+.EE
+.PP
+Each process name group (see
+.IR fork (2))
+maintains a mask of allowed drivers.
+A process can modify that mask through writes to the 
+.B drivers
+file. Messages are in the format of: 
+.IP
+.EX
+chdev \f2op\fP \f2devmask\fP
+.EE
+.PP
+.I Devmask
+is a string of driver characters.
+.I Op
+specifies how
+.I devmask
+is interpreted.
+.I Op
+is one of:
+.TP
+.B &
+Permit access to just the drivers specified in
+.IR devmask .
+.TP
+.B &~
+Permit access to all but the drivers specified in
+.IR devmask .
+.TP
+.B ~
+Remove access to all devices.
+.I Devmask
+is ignored.
+.PP
+Access may only be removed. No
+.I op
+permits a process to regain access to a driver
+once it has been removed.
+Removing access to
+.IR mnt (3)
+additionally prevents new mounts into the current namespace. 
+The following removes access to all drivers except
+.IR draw (3),
+.IR cons (3),
+and
+.IR fs (3):
+.IP
+.EX
+chdev & ick
 .EE
 .PP
 The
--- a/sys/src/9/port/chan.c
+++ b/sys/src/9/port/chan.c
@@ -1272,7 +1272,7 @@
 Chan*
 namec(char *aname, int amode, int omode, ulong perm)
 {
-	int len, n, t, nomount;
+	int len, n, t, nomount, devunmount;
 	Chan *c;
 	Chan *volatile cnew;
 	Path *volatile path;
@@ -1292,6 +1292,24 @@
 	name = aname;
 
 	/*
+	 * When unmounting, the name parameter must be accessed
+	 * using Aopen in order to get the real chan from
+	 * something like /srv/cs or /fd/0. However when sandboxing,
+	 * unmounting a sharp from a union is a valid operation even
+	 * if the device is blocked.
+	 */
+	devunmount = 0;
+	if(amode == Aunmount){
+		/*
+		 * Doing any walks down the device could leak information
+		 * about the existence of files.
+		 */
+		if(name[0] == '#' && utflen(name) == 2)
+			devunmount = 1;
+		amode = Aopen;
+	}
+
+	/*
 	 * Find the starting off point (the current slash, the root of
 	 * a device tree, or the current dot) as well as the name to
 	 * evaluate starting there.
@@ -1313,24 +1331,13 @@
 			up->genbuf[n++] = *name++;
 		}
 		up->genbuf[n] = '\0';
-		/*
-		 *  noattach is sandboxing.
-		 *
-		 *  the OK exceptions are:
-		 *	|  it only gives access to pipes you create
-		 *	d  this process's file descriptors
-		 *	e  this process's environment
-		 *  the iffy exceptions are:
-		 *	c  time and pid, but also cons and consctl
-		 *	p  control of your own processes (and unfortunately
-		 *	   any others left unprotected)
-		 */
 		n = chartorune(&r, up->genbuf+1)+1;
-		if(up->pgrp->noattach && utfrune("|decp", r)==nil)
-			error(Enoattach);
 		t = devno(r, 1);
 		if(t == -1)
 			error(Ebadsharp);
+		if(!devunmount && !devallowed(up->pgrp, r))
+			error(Enoattach);
+
 		c = devtab[t]->attach(up->genbuf+n);
 		break;
 
--- a/sys/src/9/port/dev.c
+++ b/sys/src/9/port/dev.c
@@ -31,6 +31,63 @@
 }
 
 void
+devmask(Pgrp *pgrp, int invert, char *devs)
+{
+	int i, t, w;
+	char *p;
+	Rune r;
+	u64int mask[nelem(pgrp->notallowed)];
+
+	if(invert)
+		memset(mask, 0xFF, sizeof mask);
+	else		
+		memset(mask, 0, sizeof mask);		
+
+	w = sizeof mask[0] * 8;
+	for(p = devs; *p != '\0';){
+		p += chartorune(&r, p);
+		t = devno(r, 1);
+		if(t == -1)
+			continue;
+		if(invert)
+			mask[t/w] &= ~(1<<t%w);
+		else
+			mask[t/w] |= 1<<t%w;
+	}
+
+	wlock(&pgrp->ns);
+	for(i=0; i < nelem(pgrp->notallowed); i++)
+		pgrp->notallowed[i] |= mask[i];
+	wunlock(&pgrp->ns);
+}
+
+int
+devallowed(Pgrp *pgrp, int r)
+{
+	int t, w, b;
+
+	t = devno(r, 1);
+	if(t == -1)
+		return 0;
+
+	w = sizeof(u64int) * 8;
+	rlock(&pgrp->ns);
+	b = !(pgrp->notallowed[t/w] & 1<<t%w);
+	runlock(&pgrp->ns);
+	return b;
+}
+
+int
+canmount(Pgrp *pgrp)
+{
+	/*
+	 * Devmnt is not usable directly from user procs, so
+	 * having it removed is interpreted to block any mounts.
+	 */
+	return devallowed(pgrp, 'M');
+}
+
+void
 devdir(Chan *c, Qid qid, char *n, vlong length, char *user, long perm, Dir *db)
 {
 	db->name = n;
--- a/sys/src/9/port/devcons.c
+++ b/sys/src/9/port/devcons.c
@@ -39,6 +39,16 @@
 	CMrdb,		"rdb",		0,
 };
 
+enum
+{
+	CMchdev,
+};
+
+Cmdtab drivermsg[] = 
+{
+	CMchdev,	"chdev",	0,
+};
+
 void
 printinit(void)
 {
@@ -332,7 +342,7 @@
 	"cons",		{Qcons},	0,		0660,
 	"consctl",	{Qconsctl},	0,		0220,
 	"cputime",	{Qcputime},	6*NUMSIZE,	0444,
-	"drivers",	{Qdrivers},	0,		0444,
+	"drivers",	{Qdrivers},	0,		0666,
 	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
 	"hostowner",	{Qhostowner},	0,		0664,
 	"kmesg",	{Qkmesg},	0,		0440,
@@ -583,9 +593,15 @@
 	case Qdrivers:
 		b = smalloc(READSTR);
 		k = 0;
-		for(i = 0; devtab[i] != nil; i++)
+		
+		rlock(&up->pgrp->ns);
+		for(i = 0; devtab[i] != nil; i++){
+			if(up->pgrp->notallowed[i/(sizeof(u64int)*8)] & 1<<i%(sizeof(u64int)*8))
+				continue;
 			k += snprint(b+k, READSTR-k, "#%C %s\n",
 				devtab[i]->dc, devtab[i]->name);
+		}
+		runlock(&up->pgrp->ns);
 		if(waserror()){
 			free(b);
 			nexterror();
@@ -620,6 +636,7 @@
 {
 	char buf[256];
 	long l, bp;
+	int invert;
 	char *a;
 	Mach *mp;
 	int id;
@@ -674,6 +691,41 @@
 
 	case Qconfig:
 		error(Eperm);
+		break;
+
+	case Qdrivers:
+		cb = parsecmd(a, n);
+		if(waserror()) {
+			free(cb);
+			nexterror();
+		}
+
+		ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
+		if(ct == nil)
+			error(Ebadarg);
+		if(ct->index != CMchdev)
+			error(Ebadarg);
+		if(cb->nf < 2 || cb->nf > 3)
+			error(Ebadarg);
+
+		invert = 1;
+		a = cb->f[2];
+		switch(cb->f[1][0]){
+		case '&':
+			if(cb->f[1][1] == '~')
+				invert--;
+			break;
+		case '~':
+			a = "";
+			break;
+		default:
+			error(Ebadarg);
+			break;
+		}
+
+		devmask(up->pgrp, invert, a);
+		poperror();
+		free(cb);
 		break;
 
 	case Qreboot:
--- a/sys/src/9/port/devshr.c
+++ b/sys/src/9/port/devshr.c
@@ -464,7 +464,7 @@
 		cclose(c);
 		return nc;	
 	case Qcroot:
-		if(up->pgrp->noattach)
+		if(!canmount(up->pgrp))
 			error(Enoattach);
 		if((perm & DMDIR) == 0 || mode != OREAD)
 			error(Eperm);
@@ -498,7 +498,7 @@
 		sch->shr = shr;
 		break;
 	case Qcshr:
-		if(up->pgrp->noattach)
+		if(!canmount(up->pgrp))
 			error(Enoattach);
 		if((perm & DMDIR) != 0 || mode != OWRITE)
 			error(Eperm);
@@ -731,7 +731,7 @@
 	Mhead *h;
 	Mount *m;
 
-	if(up->pgrp->noattach)
+	if(!canmount(up->pgrp))
 		error(Enoattach);
 	sch = tosch(c);
 	if(sch->level != Qcmpt)
--- a/sys/src/9/port/mkdevc
+++ b/sys/src/9/port/mkdevc
@@ -78,6 +78,9 @@
 	if(ARGC < 2)
 		exit "usage"
 
+	if(ndev >= 256)
+		exit "device count will overflow Pgrp.notallowed"
+
 	printf "#include \"u.h\"\n";
 	printf "#include \"../port/lib.h\"\n";
 	printf "#include \"mem.h\"\n";
--- a/sys/src/9/port/portdat.h
+++ b/sys/src/9/port/portdat.h
@@ -121,6 +121,7 @@
 	Amount,				/* to be mounted or mounted upon */
 	Acreate,			/* is to be created */
 	Aremove,			/* will be removed by caller */
+	Aunmount,			/* unmount arg[0] */
 
 	COPEN	= 0x0001,		/* for i/o */
 	CMSG	= 0x0002,		/* the message channel for a mount */
@@ -484,7 +485,7 @@
 {
 	Ref;
 	RWlock	ns;			/* Namespace n read/one write lock */
-	int	noattach;
+	u64int	notallowed[4];		/* Room for 256 devices */
 	Mhead	*mnthash[MNTHASH];
 };
 
--- a/sys/src/9/port/portfns.h
+++ b/sys/src/9/port/portfns.h
@@ -413,6 +413,9 @@
 ushort		nhgets(void*);
 ulong		µs(void);
 long		lcycles(void);
+void		devmask(Pgrp*,int,char*);
+int		devallowed(Pgrp*, int);
+int		canmount(Pgrp*);
 
 #pragma varargck argpos iprint	1
 #pragma	varargck argpos	panic	1
--- a/sys/src/9/port/sysfile.c
+++ b/sys/src/9/port/sysfile.c
@@ -1048,7 +1048,7 @@
 			nexterror();
 		}
 
-		if(up->pgrp->noattach)
+		if(!canmount(up->pgrp))
 			error(Enoattach);
 
 		ac = nil;
@@ -1160,14 +1160,8 @@
 		nexterror();
 	}
 	if(name != nil) {
-		/*
-		 * This has to be namec(..., Aopen, ...) because
-		 * if arg[0] is something like /srv/cs or /fd/0,
-		 * opening it is the only way to get at the real
-		 * Chan underneath.
-		 */
 		validaddr((uintptr)name, 1, 0);
-		cmounted = namec(name, Aopen, OREAD, 0);
+		cmounted = namec(name, Aunmount, OREAD, 0);
 	}
 	cunmount(cmount, cmounted);
 	poperror();
--- a/sys/src/9/port/sysproc.c
+++ b/sys/src/9/port/sysproc.c
@@ -34,6 +34,7 @@
 	Egrp *oeg;
 	ulong pid, flag;
 	Mach *wm;
+	char *devs;
 
 	flag = va_arg(list, ulong);
 	/* Check flags before we commit */
@@ -44,6 +45,11 @@
 	if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG))
 		error(Ebadarg);
 
+	/*
+	 * Code using RFNOMNT expects to block all but
+	 * the following devices.
+	 */
+	devs = "|decp";
 	if((flag&RFPROC) == 0) {
 		if(flag & (RFMEM|RFNOWAIT))
 			error(Ebadarg);
@@ -60,12 +66,12 @@
 			up->pgrp = newpgrp();
 			if(flag & RFNAMEG)
 				pgrpcpy(up->pgrp, opg);
-			/* inherit noattach */
-			up->pgrp->noattach = opg->noattach;
+			/* inherit notallowed */
+			memmove(up->pgrp->notallowed, opg->notallowed, sizeof up->pgrp->notallowed);
 			closepgrp(opg);
 		}
 		if(flag & RFNOMNT)
-			up->pgrp->noattach = 1;
+			devmask(up->pgrp, 1, devs);
 		if(flag & RFREND) {
 			org = up->rgrp;
 			up->rgrp = newrgrp();
@@ -177,8 +183,8 @@
 		p->pgrp = newpgrp();
 		if(flag & RFNAMEG)
 			pgrpcpy(p->pgrp, up->pgrp);
-		/* inherit noattach */
-		p->pgrp->noattach = up->pgrp->noattach;
+		/* inherit notallowed */
+		memmove(p->pgrp->notallowed, up->pgrp->notallowed, sizeof p->pgrp->notallowed);
 	}
 	else {
 		p->pgrp = up->pgrp;
@@ -185,7 +191,7 @@
 		incref(p->pgrp);
 	}
 	if(flag & RFNOMNT)
-		p->pgrp->noattach = 1;
+		devmask(p->pgrp, 1, devs);
 
 	if(flag & RFREND)
 		p->rgrp = newrgrp();