* [9front] [PATCH] Unmount to remove sharp devices.
@ 2022-05-04 14:09 Jacob Moody
2022-05-04 15:05 ` ori
2022-05-04 15:31 ` ori
0 siblings, 2 replies; 18+ messages in thread
From: Jacob Moody @ 2022-05-04 14:09 UTC (permalink / raw)
To: 9front
Hello,
This patch allows processes to unmount sharp devices to prevent itself and its children from accessing
them. This is implemented through an internal rework of how RFNOMNT works, making RFNOMNT a special
case of setting disallowed devices. To replicate the mount blocking functionality of RFNOMNT a special
case is given for blocking devmnt, which also blocks the process and its children from making any mount
calls.
If everything passes the sniff test I can commit these changes. Diff is here: http://okturing.com/src/13574/body and included
below.
Thanks,
moody
diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
--- a//sys/man/3/0intro
+++ b//sys/man/3/0intro
@@ -87,6 +87,20 @@
commands will need quotes to protect the
.B #
characters.
+.PP
+Access to a kernel device can be forfeited by
+unmounting it. For example, after
+.IP
+.EX
+unmount(nil, "#c")
+.EE
+.LP
+the calling process and its children can no longer access the device through its
+sharp name. Existing binds of these devices are untouched.
+Unmounting the mount device (see
+.IR mnt (3))
+has the side effect of preventing the calling process and its children
+from performing mounts.
.SH SEE ALSO
.IR intro (5),
.IR intro (2)
--- a//sys/src/9/port/chan.c
+++ b//sys/src/9/port/chan.c
@@ -1313,24 +1313,19 @@
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);
+ /*
+ * Notallowed is sandboxing.
+ * A process group can willingly give up access to a
+ * sharp device by unmounting it. Notallowed itself is
+ * a bitmask of indicies in to devtab, a high bit indicating
+ * that the process has given up its right to use that device.
+ */
+ if((up->pgrp->notallowed[t/(sizeof(u64int)*8)] & 1<<(t%(sizeof(u64int)*8))) != 0)
+ error(Enoattach);
c = devtab[t]->attach(up->genbuf+n);
break;
--- 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, 1))
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, 1))
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, 1))
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
@@ -484,7 +484,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,7 @@
ushort nhgets(void*);
ulong µs(void);
long lcycles(void);
+int canmount(Pgrp*,int);
#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, 1))
error(Enoattach);
ac = nil;
@@ -1146,6 +1146,8 @@
{
Chan *cmount, *cmounted;
char *name, *old;
+ int t;
+ Rune r;
name = va_arg(list, char*);
old = va_arg(list, char*);
@@ -1152,6 +1154,14 @@
cmounted = nil;
validaddr((uintptr)old, 1, 0);
+ if(old[0] == '#' && utflen(old) == 2){
+ chartorune(&r, old+1);
+ t = devno(r, 1);
+ if(t == -1)
+ error(Ebadsharp);
+ up->pgrp->notallowed[t/(sizeof(u64int)*8)] |= 1<<(t%(sizeof(u64int)*8));
+ return 0;
+ }
cmount = namec(old, Amount, 0, 0);
if(waserror()) {
cclose(cmount);
--- a//sys/src/9/port/sysproc.c
+++ b//sys/src/9/port/sysproc.c
@@ -23,6 +23,43 @@
pexit("fork aborted", 1);
}
+int
+canmount(Pgrp *p, int user)
+{
+ int t;
+
+ /*
+ * Devmnt is not usable directly from user procs, so
+ * having it removed is interpreted to block any mounts.
+ */
+ t = devno('M', user);
+ if(t != -1 && (p->notallowed[t/(sizeof(u64int)*8)] & 1<<(t%(sizeof(u64int)*8))) != 0)
+ return 0;
+ return 1;
+}
+
+static void
+setrfnomnt(Pgrp *p)
+{
+ int t, i;
+ u64int mask[nelem(p->notallowed)];
+ char allowed[] = "|decp";
+
+ /*
+ * Code using RFNOMNT expects to block all sharp devices
+ * and mounting except for the devices defined in allowed[]
+ */
+ for(i=0; i < sizeof allowed; i++){
+ t = devno(allowed[i], 1);
+ if(t == -1)
+ continue;
+ mask[t/(sizeof(u64int)*8)] |= 1<<(t%(sizeof(u64int)*8));
+ }
+ /* Sets bit for devmnt, so all mounts are disabled implicitly */
+ for(i=0; i < nelem(p->notallowed); i++)
+ p->notallowed[i] |= ~mask[i];
+}
+
uintptr
sysrfork(va_list list)
{
@@ -60,12 +97,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;
+ setrfnomnt(up->pgrp);
if(flag & RFREND) {
org = up->rgrp;
up->rgrp = newrgrp();
@@ -177,8 +214,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 +222,7 @@
incref(p->pgrp);
}
if(flag & RFNOMNT)
- p->pgrp->noattach = 1;
+ setrfnomnt(p->pgrp);
if(flag & RFREND)
p->rgrp = newrgrp();
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 14:09 [9front] [PATCH] Unmount to remove sharp devices Jacob Moody
@ 2022-05-04 15:05 ` ori
2022-05-04 15:31 ` ori
1 sibling, 0 replies; 18+ messages in thread
From: ori @ 2022-05-04 15:05 UTC (permalink / raw)
To: 9front
Quoth Jacob Moody <moody@mail.posixcafe.org>:
> Hello,
>
> This patch allows processes to unmount sharp devices to prevent itself and its children from accessing
> them. This is implemented through an internal rework of how RFNOMNT works, making RFNOMNT a special
> case of setting disallowed devices. To replicate the mount blocking functionality of RFNOMNT a special
> case is given for blocking devmnt, which also blocks the process and its children from making any mount
> calls.
>
> If everything passes the sniff test I can commit these changes. Diff is here: http://okturing.com/src/13574/body and included
> below.
>
> Thanks,
> moody
>
Very nice -- I'm going to try to apply the patch and port shithub
scripts to use it this weekend. Thinking about how I'd use it,
I wonder if there's a clean way to remove all devices without
disabling mounts, since blacklists are a bit of a pain. Maybe
unmount `{awk '/^#[^keep]/{print $1} /dev/drivers}
is good enough.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 14:09 [9front] [PATCH] Unmount to remove sharp devices Jacob Moody
2022-05-04 15:05 ` ori
@ 2022-05-04 15:31 ` ori
2022-05-04 16:15 ` Stanley Lieber
2022-05-04 17:41 ` Lyndon Nerenberg (VE7TFX/VE6BBM)
1 sibling, 2 replies; 18+ messages in thread
From: ori @ 2022-05-04 15:31 UTC (permalink / raw)
To: 9front
Quoth Jacob Moody <moody@mail.posixcafe.org>:
> Hello,
>
> This patch allows processes to unmount sharp devices to prevent itself and its children from accessing
> them. This is implemented through an internal rework of how RFNOMNT works, making RFNOMNT a special
> case of setting disallowed devices. To replicate the mount blocking functionality of RFNOMNT a special
> case is given for blocking devmnt, which also blocks the process and its children from making any mount
> calls.
>
> If everything passes the sniff test I can commit these changes. Diff is here: http://okturing.com/src/13574/body and included
> below.
>
> Thanks,
> moody
>
also -- are there places that you want to take
advantage of this within the system? It'd be
good to use the feature, rather than just having
it sit around.
git/serve? rc-httpd? maybe some other daemons
could use some sandboxing.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 15:31 ` ori
@ 2022-05-04 16:15 ` Stanley Lieber
2022-05-04 17:41 ` Lyndon Nerenberg (VE7TFX/VE6BBM)
1 sibling, 0 replies; 18+ messages in thread
From: Stanley Lieber @ 2022-05-04 16:15 UTC (permalink / raw)
To: 9front
this is very welcome.
one problem with getting rid of mounts and binds is programs that use mounts and binds.
sl
> On May 4, 2022, at 11:32 AM, ori@eigenstate.org wrote:
>
> Quoth Jacob Moody <moody@mail.posixcafe.org>:
>> Hello,
>>
>> This patch allows processes to unmount sharp devices to prevent itself and its children from accessing
>> them. This is implemented through an internal rework of how RFNOMNT works, making RFNOMNT a special
>> case of setting disallowed devices. To replicate the mount blocking functionality of RFNOMNT a special
>> case is given for blocking devmnt, which also blocks the process and its children from making any mount
>> calls.
>>
>> If everything passes the sniff test I can commit these changes. Diff is here: http://okturing.com/src/13574/body and included
>> below.
>>
>> Thanks,
>> moody
>>
>
> also -- are there places that you want to take
> advantage of this within the system? It'd be
> good to use the feature, rather than just having
> it sit around.
>
> git/serve? rc-httpd? maybe some other daemons
> could use some sandboxing.
>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 15:31 ` ori
2022-05-04 16:15 ` Stanley Lieber
@ 2022-05-04 17:41 ` Lyndon Nerenberg (VE7TFX/VE6BBM)
2022-05-04 17:55 ` Jacob Moody
1 sibling, 1 reply; 18+ messages in thread
From: Lyndon Nerenberg (VE7TFX/VE6BBM) @ 2022-05-04 17:41 UTC (permalink / raw)
To: 9front
> git/serve? rc-httpd? maybe some other daemons
> could use some sandboxing.
The FTP and TFTP servers are obvious targets. But what would be
really cool is if this could be integrated into the namespace
files. That way you could set up the namespace for something like
FTP, and then remove the ability to mount #foo without having to
touch the FTP code itself. I could see myself using this all over
the place.
--lyndon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 17:41 ` Lyndon Nerenberg (VE7TFX/VE6BBM)
@ 2022-05-04 17:55 ` Jacob Moody
2022-05-05 1:59 ` Alex Musolino
0 siblings, 1 reply; 18+ messages in thread
From: Jacob Moody @ 2022-05-04 17:55 UTC (permalink / raw)
To: 9front
On 5/4/22 11:41, Lyndon Nerenberg (VE7TFX/VE6BBM) wrote:
>> git/serve? rc-httpd? maybe some other daemons
>> could use some sandboxing.
>
> The FTP and TFTP servers are obvious targets. But what would be
> really cool is if this could be integrated into the namespace
> files. That way you could set up the namespace for something like
> FTP, and then remove the ability to mount #foo without having to
> touch the FTP code itself. I could see myself using this all over
> the place.
>
> --lyndon
This is already true to some extent as is with this patch.
Adding an 'unmount #e' to your namespace file will remove
access to #e as is. However I want to look a bit more
at doing the inverse operation, so you could say something like
"permit #e #| #p" and have it block all non listed
devices in the namespace file. I think that is more useful
to think about and avoids 10 lines of manually selecting drivers
to shoot.
Perhaps a new /lib/namespace.service could become the default
for networked services. Either way, with the kernel
modifications passing the sniff test, I will spend some
time seeing how to integrate this change in to existing daemons.
Thanks,
moody
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-04 17:55 ` Jacob Moody
@ 2022-05-05 1:59 ` Alex Musolino
2022-05-05 16:07 ` Jacob Moody
0 siblings, 1 reply; 18+ messages in thread
From: Alex Musolino @ 2022-05-05 1:59 UTC (permalink / raw)
To: 9front
> However I want to look a bit more at doing the inverse operation, so
> you could say something like "permit #e #| #p" and have it block all
> non listed devices in the namespace file. I think that is more
> useful to think about and avoids 10 lines of manually selecting
> drivers to shoot.
Absolutely. Also avoids the problem of new kernel devices being added
that then slip through, and the corresponding updates to all existing
namespace files.
Did you consider implementing this with two commands (e.g. 'drop' and
'permit') on the /dev/drivers file itself? That would make it just as
easy to do this kind of stuff from a script/program without having to
craft a namespace file.
Obviously you'd have to have the #c device available until you're
finished "dropping" and "permitting" things, but perhaps that's not
really a problem.
Just some thoughts.
--
Cheers,
Alex Musolino
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-05 1:59 ` Alex Musolino
@ 2022-05-05 16:07 ` Jacob Moody
2022-05-08 2:55 ` Jacob Moody
0 siblings, 1 reply; 18+ messages in thread
From: Jacob Moody @ 2022-05-05 16:07 UTC (permalink / raw)
To: 9front
On 5/4/22 19:59, Alex Musolino wrote:
> Did you consider implementing this with two commands (e.g. 'drop' and
> 'permit') on the /dev/drivers file itself? That would make it just as
> easy to do this kind of stuff from a script/program without having to
> craft a namespace file.
>
> Obviously you'd have to have the #c device available until you're
> finished "dropping" and "permitting" things, but perhaps that's not
> really a problem.
I think this is an excellent idea! The logic for doing permit within
the kernel is much easier, as we already have an array of all current
devices. I had started implementing a permit(1) and permit command for
namespace files and that necessitated reading /dev/drivers to get a
full list of current devices. While not a lot of code to do it, it
felt like I shouldn't have to do the inversion myself from userspace.
Unless others feel strongly about implementing device removal
through unmount I am going to plan to change the implementation
to do this instead.
Also from my understanding, dropping #c in practice shouldn't be
an issue, once you have a fd to #c/drivers, you can block #c and the fd
will still accept further writes. You may not be able to reopen
to #c/drivers file after closing that fd, but that seems completely
reasonable to me.
Thanks,
moody
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-05 16:07 ` Jacob Moody
@ 2022-05-08 2:55 ` Jacob Moody
2022-05-11 14:47 ` Jacob Moody
2022-05-12 3:18 ` ori
0 siblings, 2 replies; 18+ messages in thread
From: Jacob Moody @ 2022-05-08 2:55 UTC (permalink / raw)
To: 9front
Hello,
Gave this another go: http://okturing.com/src/13592/body
This patch includes new namepace file for rc-httpd
and changes the existing anonymous login namespace for
ftpd, both make use of the sandboxing features.
thanks,
moody
diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
--- a/lib/namespace
+++ b/lib/namespace
@@ -1,5 +1,6 @@
# root
mount -aC #s/boot /root $rootspec
+bind $rootdir'/rc' /rc
bind -a $rootdir /
# kernel devices
--- a/lib/namespace.ftp
+++ b/lib/namespace.ftp
@@ -8,5 +8,6 @@
# bind a personal incoming directory below incoming
bind -c /usr/none/incoming /usr/web/incoming/none
+permit |MedIa/
# this cuts off everything not mounted below /usr/web
bind /usr/web /
--- /tmp/diff100000742872
+++ b/lib/namespace.rc-httpd
@@ -1,0 +1,21 @@
+mount -aC #s/boot /root $rootspec
+
+# kernel devices
+bind #c /dev
+bind #d /fd
+bind -c #e /env
+bind #p /proc
+bind -a #l /net
+bind -a #I /net
+
+bind /root/$cputype/bin /bin
+bind /root/rc /rc
+bind -a /rc/bin /bin
+
+permit Mcde|plI/
+
+. /root/cfg/$sysname/namespace.rc-httpd
+
+unmount /root
+block Mc
+unmount #c /dev
--- a/sys/man/3/cons
+++ b/sys/man/3/cons
@@ -90,10 +90,30 @@
.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
+A process can forfeit access to a driver, for itself and it's future children, through a write to
+.B drivers.
+A message is one of:
+.IP "block \f2drivers\fP"
+block access to the listed
+.I drivers.
+.IP "permit \f2drivers\fP"
+permit access to just the provided
+.I drivers.
+.PP
+Drivers are identified by their short hand, the first column returned on reads,
+without the leading sharp. The following blocks access to
+.IR env (3)
+and
+.IR sd (3):
+.IP
+.EX
+block se
.EE
.PP
The
--- a/sys/man/6/namespace
+++ b/sys/man/6/namespace
@@ -59,6 +59,15 @@
.I new
is missing.
.TP
+.BI block \ drivers
+Removes access to the listed kernel
+.I drivers.
+Devices are identified by their sharp name.
+.TP
+.BI permit \ drivers
+Permit access to only the listed kernel
+.I drivers.
+.TP
.BR clear
Clear the name space with
.BR rfork(RFCNAMEG) .
@@ -80,4 +89,5 @@
.SH "SEE ALSO"
.IR bind (1),
.IR namespace (4),
-.IR init (8)
+.IR init (8),
+.IR cons (3)
--- a/sys/src/9/boot/boot.c
+++ b/sys/src/9/boot/boot.c
@@ -19,6 +19,7 @@
if(await(buf, sizeof(buf)) < 0)
goto Err;
+ bind("/root/rc", "/rc", MREPL);
bind(root, "/", MAFTER);
buf[0] = '/';
--- 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.
+ */
+ 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;
+ } else
+ devunmount = 0;
+
+ /*
* 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 && !driversallowed(up->pgrp, r))
+ error(Enoattach);
+
c = devtab[t]->attach(up->genbuf+n);
break;
--- a/sys/src/9/port/devcons.c
+++ b/sys/src/9/port/devcons.c
@@ -39,6 +39,18 @@
CMrdb, "rdb", 0,
};
+enum
+{
+ CMblock,
+ CMpermit,
+};
+
+Cmdtab drivermsg[] =
+{
+ CMblock, "block", 0,
+ CMpermit, "permit", 0,
+};
+
void
printinit(void)
{
@@ -332,7 +344,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 +595,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();
@@ -676,6 +694,26 @@
error(Eperm);
break;
+ case Qdrivers:
+ cb = parsecmd(a, n);
+
+ if(waserror()) {
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
+ switch(ct->index) {
+ case CMblock:
+ driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
+ break;
+ case CMpermit:
+ driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
+ break;
+ }
+ poperror();
+ free(cb);
+ break;
+
case Qreboot:
if(!iseve())
error(Eperm);
@@ -935,6 +973,64 @@
break;
}
return n+1;
+}
+
+void
+driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
+{
+ 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(i=0; i < argc; i++)
+ for(p = argv[i]; *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
+driversallowed(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 driversallowed(pgrp, 'M');
}
void
--- a/sys/src/9/port/devroot.c
+++ b/sys/src/9/port/devroot.c
@@ -105,6 +105,7 @@
addrootdir("net");
addrootdir("net.alt");
addrootdir("proc");
+ addrootdir("rc");
addrootdir("root");
addrootdir("srv");
addrootdir("shr");
--- 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 driverscmd(Pgrp*,int,int,char**);
+int driversallowed(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;
+ driverscmd(up->pgrp, 1, 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;
+ driverscmd(p->pgrp, 1, 1, &devs);
if(flag & RFREND)
p->rgrp = newrgrp();
--- a/sys/src/libauth/newns.c
+++ b/sys/src/libauth/newns.c
@@ -14,8 +14,8 @@
static int setenv(char*, char*);
static char *expandarg(char*, char*);
static int splitargs(char*, char*[], char*, int);
-static int nsfile(char*, Biobuf *, AuthRpc *);
-static int nsop(char*, int, char*[], AuthRpc*);
+static int nsfile(char*, Biobuf *, AuthRpc *, int);
+static int nsop(char*, int, char*[], AuthRpc*, int);
static int catch(void*, char*);
int newnsdebug;
@@ -35,7 +35,7 @@
{
Biobuf *b;
char home[4*ANAMELEN];
- int afd, cdroot;
+ int afd, cdroot, dfd;
char *path;
AuthRpc *rpc;
@@ -51,6 +51,10 @@
}
/* rpc != nil iff afd >= 0 */
+ dfd = open("#c/drivers", OWRITE|OCEXEC);
+ if(dfd < 0 && newnsdebug)
+ fprint(2, "open #c/drivers: %r\n");
+
if(file == nil){
if(!newns){
werrstr("no namespace file specified");
@@ -70,7 +74,8 @@
setenv("home", home);
}
- cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
+ cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
+ close(dfd);
Bterm(b);
freecloserpc(rpc);
@@ -87,7 +92,7 @@
}
static int
-nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
+nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
{
int argc;
char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
@@ -103,7 +108,7 @@
continue;
argc = splitargs(cmd, argv, argbuf, NARG);
if(argc)
- cdroot |= nsop(fn, argc, argv, rpc);
+ cdroot |= nsop(fn, argc, argv, rpc, dfd);
}
atnotify(catch, 0);
return cdroot;
@@ -143,7 +148,7 @@
}
static int
-nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
+nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
{
char *argv0;
ulong flags;
@@ -181,7 +186,7 @@
b = Bopen(argv[0], OREAD|OCEXEC);
if(b == nil)
return 0;
- cdroot |= nsfile(fn, b, rpc);
+ cdroot |= nsfile(fn, b, rpc, dfd);
Bterm(b);
}else if(strcmp(argv0, "clear") == 0 && argc == 0){
rfork(RFCNAMEG);
@@ -212,6 +217,14 @@
}else if(strcmp(argv0, "cd") == 0 && argc == 1){
if(chdir(argv[0]) == 0 && *argv[0] == '/')
cdroot = 1;
+ }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
+ //We should not silently fail if we can not honor a permit/block
+ //due to the parent namespace missing #c/drivers.
+ if(dfd <= 0)
+ sysfatal("%s requested, but could not open #c/drivers", argv0);
+ for(i=0; i < argc; i++)
+ if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
+ fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
}
return cdroot;
}
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-08 2:55 ` Jacob Moody
@ 2022-05-11 14:47 ` Jacob Moody
2022-05-11 16:11 ` Stanley Lieber
2022-05-12 3:18 ` ori
1 sibling, 1 reply; 18+ messages in thread
From: Jacob Moody @ 2022-05-11 14:47 UTC (permalink / raw)
To: 9front
Hello,
Another minor iteration. This tweaks the rc-httpd sandboxing
and documents it better in the man page.
thanks,
moody
diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
--- a/lib/namespace
+++ b/lib/namespace
@@ -1,5 +1,6 @@
# root
mount -aC #s/boot /root $rootspec
+bind $rootdir'/rc' /rc
bind -a $rootdir /
# kernel devices
--- a/lib/namespace.ftp
+++ b/lib/namespace.ftp
@@ -8,5 +8,6 @@
# bind a personal incoming directory below incoming
bind -c /usr/none/incoming /usr/web/incoming/none
+permit |MedIa/
# this cuts off everything not mounted below /usr/web
bind /usr/web /
--- /tmp/diff100001081045
+++ b/lib/namespace.rc-httpd
@@ -1,0 +1,19 @@
+mount -aC #s/boot /root $rootspec
+
+# kernel devices
+bind #c /dev
+bind #d /fd
+bind -c #e /env
+bind #p /proc
+
+bind /root/$cputype/bin /bin
+bind /root/rc /rc
+bind -a /rc/bin /bin
+
+permit Mcde|ps/
+
+. /root/cfg/$sysname/namespace.rc-httpd
+
+unmount /root
+block Mcs
+unmount #c /dev
--- a/rc/bin/service/!tcp80
+++ b/rc/bin/service/!tcp80
@@ -1,2 +1,2 @@
#!/bin/rc
-exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
+exec auth/newns -n /lib/namespace.rc-httpd /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
--- a/sys/man/3/cons
+++ b/sys/man/3/cons
@@ -90,10 +90,30 @@
.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
+A process can forfeit access to a driver, for itself and it's future children, through a write to
+.B drivers.
+A message is one of:
+.IP "block \f2drivers\fP"
+block access to the listed
+.I drivers.
+.IP "permit \f2drivers\fP"
+permit access to just the provided
+.I drivers.
+.PP
+Drivers are identified by their short hand, the first column returned on reads,
+without the leading sharp. The following blocks access to
+.IR env (3)
+and
+.IR sd (3):
+.IP
+.EX
+block se
.EE
.PP
The
--- a/sys/man/6/namespace
+++ b/sys/man/6/namespace
@@ -59,6 +59,15 @@
.I new
is missing.
.TP
+.BI block \ drivers
+Removes access to the listed kernel
+.I drivers.
+Devices are identified by their sharp name.
+.TP
+.BI permit \ drivers
+Permit access to only the listed kernel
+.I drivers.
+.TP
.BR clear
Clear the name space with
.BR rfork(RFCNAMEG) .
@@ -80,4 +89,5 @@
.SH "SEE ALSO"
.IR bind (1),
.IR namespace (4),
-.IR init (8)
+.IR init (8),
+.IR cons (3)
--- a/sys/man/8/rc-httpd
+++ b/sys/man/8/rc-httpd
@@ -85,6 +85,36 @@
.I REMOTE_USER
variable provides a user identification string supplied by the
client as part of user authentication.
+.SH STARTUP
+.I Rc-httpd
+is run from a file in the directory scanned by
+.IR listen (8),
+or called as an argument to aux/listen1.
+The program's standard error may be captured to a log file:
+.RS
+.EX
+exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
+.EE
+.RE
+.PP
+It is recommended to provide a strict namespace for
+.I rc-httpd.
+.B /lib/namespace.rc-httpd
+provides an example of such a namespace, and serves as
+a default.
+.PP
+When using the default,
+.B /cfg/$sysname/namespace.rc-httpd
+is sourced to allow for binding of files to a place
+.B select-handler
+may expect to find them.
+.PP
+.I Rc-httpd
+can be run using
+.IR auth/newns (8)
+to switch to the restrictive namespace before starting.
+.B /rc/bin/service/!tcp80
+provides a sample invocation.
.SH EXAMPLES
The following examples demonstrate possible ways to configure
.BR select-handler.
@@ -153,15 +183,19 @@
}
.EE
.RE
-.SH STARTUP
-.I Rc-httpd
-is run from a file in the directory scanned by
-.IR listen (8),
-or called as an argument to aux/listen1.
-The program's standard error may be captured to a log file:
+.PP
+An example
+.B /cfg/$sysname/namespace.rc-httpd:
.RS
.EX
-exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
+# #s/boot is present on /root.
+# /root is unmounted after we finish.
+
+# srv is unused, we can use it as our base FS_ROOT.
+bind /root/usr/moody/www /srv
+
+# use a specific select-handler.
+bind -b /root/usr/moody/lib/rc-httpd /rc/bin/rc-httpd
.EE
.RE
.SH FILES
@@ -190,6 +224,10 @@
.B /rc/bin/service/tcp80
.TP
.B /sys/log/www
+.TP
+.B /lib/namespace.rc-httpd
+.TP
+.B /cfg/$sysname/namespace.rc-httpd
.SH SOURCE
.B /rc/bin/rc-httpd
.SH "SEE ALSO"
--- a/sys/src/9/boot/boot.c
+++ b/sys/src/9/boot/boot.c
@@ -19,6 +19,7 @@
if(await(buf, sizeof(buf)) < 0)
goto Err;
+ bind("/root/rc", "/rc", MREPL);
bind(root, "/", MAFTER);
buf[0] = '/';
--- 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.
+ */
+ 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;
+ } else
+ devunmount = 0;
+
+ /*
* 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 && !driversallowed(up->pgrp, r))
+ error(Enoattach);
+
c = devtab[t]->attach(up->genbuf+n);
break;
--- a/sys/src/9/port/devcons.c
+++ b/sys/src/9/port/devcons.c
@@ -39,6 +39,18 @@
CMrdb, "rdb", 0,
};
+enum
+{
+ CMblock,
+ CMpermit,
+};
+
+Cmdtab drivermsg[] =
+{
+ CMblock, "block", 0,
+ CMpermit, "permit", 0,
+};
+
void
printinit(void)
{
@@ -332,7 +344,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 +595,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();
@@ -676,6 +694,26 @@
error(Eperm);
break;
+ case Qdrivers:
+ cb = parsecmd(a, n);
+
+ if(waserror()) {
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
+ switch(ct->index) {
+ case CMblock:
+ driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
+ break;
+ case CMpermit:
+ driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
+ break;
+ }
+ poperror();
+ free(cb);
+ break;
+
case Qreboot:
if(!iseve())
error(Eperm);
@@ -935,6 +973,64 @@
break;
}
return n+1;
+}
+
+void
+driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
+{
+ 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(i=0; i < argc; i++)
+ for(p = argv[i]; *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
+driversallowed(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 driversallowed(pgrp, 'M');
}
void
--- a/sys/src/9/port/devroot.c
+++ b/sys/src/9/port/devroot.c
@@ -105,6 +105,7 @@
addrootdir("net");
addrootdir("net.alt");
addrootdir("proc");
+ addrootdir("rc");
addrootdir("root");
addrootdir("srv");
addrootdir("shr");
--- 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 driverscmd(Pgrp*,int,int,char**);
+int driversallowed(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;
+ driverscmd(up->pgrp, 1, 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;
+ driverscmd(p->pgrp, 1, 1, &devs);
if(flag & RFREND)
p->rgrp = newrgrp();
--- a/sys/src/libauth/newns.c
+++ b/sys/src/libauth/newns.c
@@ -14,8 +14,8 @@
static int setenv(char*, char*);
static char *expandarg(char*, char*);
static int splitargs(char*, char*[], char*, int);
-static int nsfile(char*, Biobuf *, AuthRpc *);
-static int nsop(char*, int, char*[], AuthRpc*);
+static int nsfile(char*, Biobuf *, AuthRpc *, int);
+static int nsop(char*, int, char*[], AuthRpc*, int);
static int catch(void*, char*);
int newnsdebug;
@@ -35,7 +35,7 @@
{
Biobuf *b;
char home[4*ANAMELEN];
- int afd, cdroot;
+ int afd, cdroot, dfd;
char *path;
AuthRpc *rpc;
@@ -51,6 +51,10 @@
}
/* rpc != nil iff afd >= 0 */
+ dfd = open("#c/drivers", OWRITE|OCEXEC);
+ if(dfd < 0 && newnsdebug)
+ fprint(2, "open #c/drivers: %r\n");
+
if(file == nil){
if(!newns){
werrstr("no namespace file specified");
@@ -70,7 +74,8 @@
setenv("home", home);
}
- cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
+ cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
+ close(dfd);
Bterm(b);
freecloserpc(rpc);
@@ -87,7 +92,7 @@
}
static int
-nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
+nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
{
int argc;
char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
@@ -103,7 +108,7 @@
continue;
argc = splitargs(cmd, argv, argbuf, NARG);
if(argc)
- cdroot |= nsop(fn, argc, argv, rpc);
+ cdroot |= nsop(fn, argc, argv, rpc, dfd);
}
atnotify(catch, 0);
return cdroot;
@@ -143,7 +148,7 @@
}
static int
-nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
+nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
{
char *argv0;
ulong flags;
@@ -181,7 +186,7 @@
b = Bopen(argv[0], OREAD|OCEXEC);
if(b == nil)
return 0;
- cdroot |= nsfile(fn, b, rpc);
+ cdroot |= nsfile(fn, b, rpc, dfd);
Bterm(b);
}else if(strcmp(argv0, "clear") == 0 && argc == 0){
rfork(RFCNAMEG);
@@ -212,6 +217,14 @@
}else if(strcmp(argv0, "cd") == 0 && argc == 1){
if(chdir(argv[0]) == 0 && *argv[0] == '/')
cdroot = 1;
+ }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
+ //We should not silently fail if we can not honor a permit/block
+ //due to the parent namespace missing #c/drivers.
+ if(dfd <= 0)
+ sysfatal("%s requested, but could not open #c/drivers", argv0);
+ for(i=0; i < argc; i++)
+ if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
+ fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
}
return cdroot;
}
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-11 14:47 ` Jacob Moody
@ 2022-05-11 16:11 ` Stanley Lieber
2022-05-12 4:29 ` Jacob Moody
0 siblings, 1 reply; 18+ messages in thread
From: Stanley Lieber @ 2022-05-11 16:11 UTC (permalink / raw)
To: 9front
in some cases, would it make more sense to invoke auth/newns at the listener level, rather than every time rc-httpd is called?
al
> On May 11, 2022, at 10:49 AM, Jacob Moody <moody@mail.posixcafe.org> wrote:
>
> Hello,
>
> Another minor iteration. This tweaks the rc-httpd sandboxing
> and documents it better in the man page.
>
> thanks,
> moody
>
> diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
> --- a/lib/namespace
> +++ b/lib/namespace
> @@ -1,5 +1,6 @@
> # root
> mount -aC #s/boot /root $rootspec
> +bind $rootdir'/rc' /rc
> bind -a $rootdir /
>
> # kernel devices
> --- a/lib/namespace.ftp
> +++ b/lib/namespace.ftp
> @@ -8,5 +8,6 @@
> # bind a personal incoming directory below incoming
> bind -c /usr/none/incoming /usr/web/incoming/none
>
> +permit |MedIa/
> # this cuts off everything not mounted below /usr/web
> bind /usr/web /
> --- /tmp/diff100001081045
> +++ b/lib/namespace.rc-httpd
> @@ -1,0 +1,19 @@
> +mount -aC #s/boot /root $rootspec
> +
> +# kernel devices
> +bind #c /dev
> +bind #d /fd
> +bind -c #e /env
> +bind #p /proc
> +
> +bind /root/$cputype/bin /bin
> +bind /root/rc /rc
> +bind -a /rc/bin /bin
> +
> +permit Mcde|ps/
> +
> +. /root/cfg/$sysname/namespace.rc-httpd
> +
> +unmount /root
> +block Mcs
> +unmount #c /dev
> --- a/rc/bin/service/!tcp80
> +++ b/rc/bin/service/!tcp80
> @@ -1,2 +1,2 @@
> #!/bin/rc
> -exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +exec auth/newns -n /lib/namespace.rc-httpd /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> --- a/sys/man/3/cons
> +++ b/sys/man/3/cons
> @@ -90,10 +90,30 @@
> .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
> +A process can forfeit access to a driver, for itself and it's future children, through a write to
> +.B drivers.
> +A message is one of:
> +.IP "block \f2drivers\fP"
> +block access to the listed
> +.I drivers.
> +.IP "permit \f2drivers\fP"
> +permit access to just the provided
> +.I drivers.
> +.PP
> +Drivers are identified by their short hand, the first column returned on reads,
> +without the leading sharp. The following blocks access to
> +.IR env (3)
> +and
> +.IR sd (3):
> +.IP
> +.EX
> +block se
> .EE
> .PP
> The
> --- a/sys/man/6/namespace
> +++ b/sys/man/6/namespace
> @@ -59,6 +59,15 @@
> .I new
> is missing.
> .TP
> +.BI block \ drivers
> +Removes access to the listed kernel
> +.I drivers.
> +Devices are identified by their sharp name.
> +.TP
> +.BI permit \ drivers
> +Permit access to only the listed kernel
> +.I drivers.
> +.TP
> .BR clear
> Clear the name space with
> .BR rfork(RFCNAMEG) .
> @@ -80,4 +89,5 @@
> .SH "SEE ALSO"
> .IR bind (1),
> .IR namespace (4),
> -.IR init (8)
> +.IR init (8),
> +.IR cons (3)
> --- a/sys/man/8/rc-httpd
> +++ b/sys/man/8/rc-httpd
> @@ -85,6 +85,36 @@
> .I REMOTE_USER
> variable provides a user identification string supplied by the
> client as part of user authentication.
> +.SH STARTUP
> +.I Rc-httpd
> +is run from a file in the directory scanned by
> +.IR listen (8),
> +or called as an argument to aux/listen1.
> +The program's standard error may be captured to a log file:
> +.RS
> +.EX
> +exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +.EE
> +.RE
> +.PP
> +It is recommended to provide a strict namespace for
> +.I rc-httpd.
> +.B /lib/namespace.rc-httpd
> +provides an example of such a namespace, and serves as
> +a default.
> +.PP
> +When using the default,
> +.B /cfg/$sysname/namespace.rc-httpd
> +is sourced to allow for binding of files to a place
> +.B select-handler
> +may expect to find them.
> +.PP
> +.I Rc-httpd
> +can be run using
> +.IR auth/newns (8)
> +to switch to the restrictive namespace before starting.
> +.B /rc/bin/service/!tcp80
> +provides a sample invocation.
> .SH EXAMPLES
> The following examples demonstrate possible ways to configure
> .BR select-handler.
> @@ -153,15 +183,19 @@
> }
> .EE
> .RE
> -.SH STARTUP
> -.I Rc-httpd
> -is run from a file in the directory scanned by
> -.IR listen (8),
> -or called as an argument to aux/listen1.
> -The program's standard error may be captured to a log file:
> +.PP
> +An example
> +.B /cfg/$sysname/namespace.rc-httpd:
> .RS
> .EX
> -exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +# #s/boot is present on /root.
> +# /root is unmounted after we finish.
> +
> +# srv is unused, we can use it as our base FS_ROOT.
> +bind /root/usr/moody/www /srv
> +
> +# use a specific select-handler.
> +bind -b /root/usr/moody/lib/rc-httpd /rc/bin/rc-httpd
> .EE
> .RE
> .SH FILES
> @@ -190,6 +224,10 @@
> .B /rc/bin/service/tcp80
> .TP
> .B /sys/log/www
> +.TP
> +.B /lib/namespace.rc-httpd
> +.TP
> +.B /cfg/$sysname/namespace.rc-httpd
> .SH SOURCE
> .B /rc/bin/rc-httpd
> .SH "SEE ALSO"
> --- a/sys/src/9/boot/boot.c
> +++ b/sys/src/9/boot/boot.c
> @@ -19,6 +19,7 @@
> if(await(buf, sizeof(buf)) < 0)
> goto Err;
>
> + bind("/root/rc", "/rc", MREPL);
> bind(root, "/", MAFTER);
>
> buf[0] = '/';
> --- 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.
> + */
> + 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;
> + } else
> + devunmount = 0;
> +
> + /*
> * 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 && !driversallowed(up->pgrp, r))
> + error(Enoattach);
> +
> c = devtab[t]->attach(up->genbuf+n);
> break;
>
> --- a/sys/src/9/port/devcons.c
> +++ b/sys/src/9/port/devcons.c
> @@ -39,6 +39,18 @@
> CMrdb, "rdb", 0,
> };
>
> +enum
> +{
> + CMblock,
> + CMpermit,
> +};
> +
> +Cmdtab drivermsg[] =
> +{
> + CMblock, "block", 0,
> + CMpermit, "permit", 0,
> +};
> +
> void
> printinit(void)
> {
> @@ -332,7 +344,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 +595,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();
> @@ -676,6 +694,26 @@
> error(Eperm);
> break;
>
> + case Qdrivers:
> + cb = parsecmd(a, n);
> +
> + if(waserror()) {
> + free(cb);
> + nexterror();
> + }
> + ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
> + switch(ct->index) {
> + case CMblock:
> + driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
> + break;
> + case CMpermit:
> + driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
> + break;
> + }
> + poperror();
> + free(cb);
> + break;
> +
> case Qreboot:
> if(!iseve())
> error(Eperm);
> @@ -935,6 +973,64 @@
> break;
> }
> return n+1;
> +}
> +
> +void
> +driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
> +{
> + 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(i=0; i < argc; i++)
> + for(p = argv[i]; *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
> +driversallowed(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 driversallowed(pgrp, 'M');
> }
>
> void
> --- a/sys/src/9/port/devroot.c
> +++ b/sys/src/9/port/devroot.c
> @@ -105,6 +105,7 @@
> addrootdir("net");
> addrootdir("net.alt");
> addrootdir("proc");
> + addrootdir("rc");
> addrootdir("root");
> addrootdir("srv");
> addrootdir("shr");
> --- 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 driverscmd(Pgrp*,int,int,char**);
> +int driversallowed(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;
> + driverscmd(up->pgrp, 1, 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;
> + driverscmd(p->pgrp, 1, 1, &devs);
>
> if(flag & RFREND)
> p->rgrp = newrgrp();
> --- a/sys/src/libauth/newns.c
> +++ b/sys/src/libauth/newns.c
> @@ -14,8 +14,8 @@
> static int setenv(char*, char*);
> static char *expandarg(char*, char*);
> static int splitargs(char*, char*[], char*, int);
> -static int nsfile(char*, Biobuf *, AuthRpc *);
> -static int nsop(char*, int, char*[], AuthRpc*);
> +static int nsfile(char*, Biobuf *, AuthRpc *, int);
> +static int nsop(char*, int, char*[], AuthRpc*, int);
> static int catch(void*, char*);
>
> int newnsdebug;
> @@ -35,7 +35,7 @@
> {
> Biobuf *b;
> char home[4*ANAMELEN];
> - int afd, cdroot;
> + int afd, cdroot, dfd;
> char *path;
> AuthRpc *rpc;
>
> @@ -51,6 +51,10 @@
> }
> /* rpc != nil iff afd >= 0 */
>
> + dfd = open("#c/drivers", OWRITE|OCEXEC);
> + if(dfd < 0 && newnsdebug)
> + fprint(2, "open #c/drivers: %r\n");
> +
> if(file == nil){
> if(!newns){
> werrstr("no namespace file specified");
> @@ -70,7 +74,8 @@
> setenv("home", home);
> }
>
> - cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
> + cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
> + close(dfd);
> Bterm(b);
> freecloserpc(rpc);
>
> @@ -87,7 +92,7 @@
> }
>
> static int
> -nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
> +nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
> {
> int argc;
> char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
> @@ -103,7 +108,7 @@
> continue;
> argc = splitargs(cmd, argv, argbuf, NARG);
> if(argc)
> - cdroot |= nsop(fn, argc, argv, rpc);
> + cdroot |= nsop(fn, argc, argv, rpc, dfd);
> }
> atnotify(catch, 0);
> return cdroot;
> @@ -143,7 +148,7 @@
> }
>
> static int
> -nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
> +nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
> {
> char *argv0;
> ulong flags;
> @@ -181,7 +186,7 @@
> b = Bopen(argv[0], OREAD|OCEXEC);
> if(b == nil)
> return 0;
> - cdroot |= nsfile(fn, b, rpc);
> + cdroot |= nsfile(fn, b, rpc, dfd);
> Bterm(b);
> }else if(strcmp(argv0, "clear") == 0 && argc == 0){
> rfork(RFCNAMEG);
> @@ -212,6 +217,14 @@
> }else if(strcmp(argv0, "cd") == 0 && argc == 1){
> if(chdir(argv[0]) == 0 && *argv[0] == '/')
> cdroot = 1;
> + }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
> + //We should not silently fail if we can not honor a permit/block
> + //due to the parent namespace missing #c/drivers.
> + if(dfd <= 0)
> + sysfatal("%s requested, but could not open #c/drivers", argv0);
> + for(i=0; i < argc; i++)
> + if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
> + fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
> }
> return cdroot;
> }
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-08 2:55 ` Jacob Moody
2022-05-11 14:47 ` Jacob Moody
@ 2022-05-12 3:18 ` ori
2022-05-12 5:10 ` Jacob Moody
1 sibling, 1 reply; 18+ messages in thread
From: ori @ 2022-05-12 3:18 UTC (permalink / raw)
To: 9front
Quoth Jacob Moody <moody@mail.posixcafe.org>:
> Hello,
>
> Gave this another go: http://okturing.com/src/13592/body
>
> This patch includes new namepace file for rc-httpd
> and changes the existing anonymous login namespace for
> ftpd, both make use of the sandboxing features.
Looking like it's getting useful :)
comments inline.
> thanks,
> moody
>
> diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
> --- a/lib/namespace
> +++ b/lib/namespace
> @@ -1,5 +1,6 @@
> # root
> mount -aC #s/boot /root $rootspec
> +bind $rootdir'/rc' /rc
> bind -a $rootdir /
Wondering why this change was needed; did a local change
leak in?
>
> # kernel devices
> --- a/lib/namespace.ftp
> +++ b/lib/namespace.ftp
> @@ -8,5 +8,6 @@
> # bind a personal incoming directory below incoming
> bind -c /usr/none/incoming /usr/web/incoming/none
>
> +permit |MedIa/
> # this cuts off everything not mounted below /usr/web
> bind /usr/web /
> --- /tmp/diff100000742872
> +++ b/lib/namespace.rc-httpd
> @@ -1,0 +1,21 @@
> +mount -aC #s/boot /root $rootspec
> +
> +# kernel devices
> +bind #c /dev
> +bind #d /fd
> +bind -c #e /env
> +bind #p /proc
> +bind -a #l /net
> +bind -a #I /net
duplicated line?
> +
> +bind /root/$cputype/bin /bin
> +bind /root/rc /rc
> +bind -a /rc/bin /bin
> +
> +permit Mcde|plI/
> +
> +. /root/cfg/$sysname/namespace.rc-httpd
> +
> +unmount /root
> +block Mc
> +unmount #c /dev
> --- a/sys/man/3/cons
> +++ b/sys/man/3/cons
> @@ -90,10 +90,30 @@
> .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
> +A process can forfeit access to a driver, for itself and it's future children, through a write to
> +.B drivers.
Is this accurate? I'd expect the way this is implement to affect all procs
in the current namespace, not just the current one and its children.
Maybe: 'a process can eject a driver from
the current namespace through a write to
drivers'.
> +A message is one of:
> +.IP "block \f2drivers\fP"
> +block access to the listed
> +.I drivers.
> +.IP "permit \f2drivers\fP"
> +permit access to just the provided
> +.I drivers.
> +.PP
> +Drivers are identified by their short hand, the first column returned on reads,
> +without the leading sharp. The following blocks access to
Clunky phrasing. Perhaps '\f2drivers\f2 is a string of driver characters'.
May also need to mention the implicication of disallowing 'M'.
> +.IR env (3)
> +and
> +.IR sd (3):
> +.IP
> +.EX
> +block se
> .EE
> .PP
> The
> --- a/sys/man/6/namespace
> +++ b/sys/man/6/namespace
> @@ -59,6 +59,15 @@
> .I new
> is missing.
> .TP
> +.BI block \ drivers
> +Removes access to the listed kernel
> +.I drivers.
> +Devices are identified by their sharp name.
> +.TP
> +.BI permit \ drivers
> +Permit access to only the listed kernel
> +.I drivers.
> +.TP
> .BR clear
> Clear the name space with
> .BR rfork(RFCNAMEG) .
> @@ -80,4 +89,5 @@
> .SH "SEE ALSO"
> .IR bind (1),
> .IR namespace (4),
> -.IR init (8)
> +.IR init (8),
> +.IR cons (3)
> --- a/sys/src/9/boot/boot.c
> +++ b /sys/src/9/boot/boot.c
> @@ -19,6 +19,7 @@
> if(await(buf, sizeof(buf)) < 0)
> goto Err;
>
> + bind("/root/rc", "/rc", MREPL);
> bind(root, "/", MAFTER);
Hm. as before, I don't understand why this bind is needed:
binding /root to / should already lead to /rc being /root/rc.
>
> buf[0] = '/';
> --- 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.
> + */
> + 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;
> + } else
> + devunmount = 0;
> +
> + /*
> * 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 && !driversallowed(up->pgrp, r))
> + error(Enoattach);
> +
> c = devtab[t]->attach(up->genbuf+n);
> break;
>
> --- a/sys/src/9/port/devcons.c
> +++ b/sys/src/9/port/devcons.c
> @@ -39,6 +39,18 @@
> CMrdb, "rdb", 0,
> };
>
> +enum
> +{
> + CMblock,
> + CMpermit,
> +};
> +
> +Cmdtab drivermsg[] =
> +{
> + CMblock, "block", 0,
> + CMpermit, "permit", 0,
> +};
> +
> void
> printinit(void)
> {
> @@ -332,7 +344,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 +595,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();
> @@ -676,6 +694,26 @@
> error(Eperm);
> break;
>
> + case Qdrivers:
> + cb = parsecmd(a, n);
> +
> + if(waserror()) {
> + free(cb);
> + nexterror();
> + }
> + ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
> + switch(ct->index) {
> + case CMblock:
> + driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
> + break;
> + case CMpermit:
> + driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
> + break;
> + }
> + poperror();
> + free(cb);
> + break;
> +
> case Qreboot:
> if(!iseve())
> error(Eperm);
> @@ -935,6 +973,64 @@
> break;
> }
> return n+1;
> +}
> +
> +void
> +driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
> +{
> + 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(i=0; i < argc; i++)
> + for(p = argv[i]; *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
> +driversallowed(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 driversallowed(pgrp, 'M');
> }
>
> void
> --- a/sys/src/9/port/devroot.c
> +++ b/sys/src/9/port/devroot.c
> @@ -105,6 +105,7 @@
> addrootdir("net");
> addrootdir("net.alt");
> addrootdir("proc");
> + addrootdir("rc");
> addrootdir("root");
> addrootdir("srv");
> addrootdir("shr");
> --- 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 driverscmd(Pgrp*,int,int,char**);
> +int driversallowed(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;
> + driverscmd(up->pgrp, 1, 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;
> + driverscmd(p->pgrp, 1, 1, &devs);
>
> if(flag & RFREND)
> p->rgrp = newrgrp();
> --- a/sys/src/libauth/newns.c
> +++ b/sys/src/libauth/newns.c
> @@ -14,8 +14,8 @@
> static int setenv(char*, char*);
> static char *expandarg(char*, char*);
> static int splitargs(char*, char*[], char*, int);
> -static int nsfile(char*, Biobuf *, AuthRpc *);
> -static int nsop(char*, int, char*[], AuthRpc*);
> +static int nsfile(char*, Biobuf *, AuthRpc *, int);
> +static int nsop(char*, int, char*[], AuthRpc*, int);
> static int catch(void*, char*);
>
> int newnsdebug;
> @@ -35,7 +35,7 @@
> {
> Biobuf *b;
> char home[4*ANAMELEN];
> - int afd, cdroot;
> + int afd, cdroot, dfd;
> char *path;
> AuthRpc *rpc;
>
> @@ -51,6 +51,10 @@
> }
> /* rpc != nil iff afd >= 0 */
>
> + dfd = open("#c/drivers", OWRITE|OCEXEC);
> + if(dfd < 0 && newnsdebug)
> + fprint(2, "open #c/drivers: %r\n");
> +
> if(file == nil){
> if(!newns){
> werrstr("no namespace file specified");
> @@ -70,7 +74,8 @@
> setenv("home", home);
> }
>
> - cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
> + cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
> + close(dfd);
> Bterm(b);
> freecloserpc(rpc);
>
> @@ -87,7 +92,7 @@
> }
>
> static int
> -nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
> +nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
> {
> int argc;
> char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
> @@ -103,7 +108,7 @@
> continue;
> argc = splitargs(cmd, argv, argbuf, NARG);
> if(argc)
> - cdroot |= nsop(fn, argc, argv, rpc);
> + cdroot |= nsop(fn, argc, argv, rpc, dfd);
> }
> atnotify(catch, 0);
> return cdroot;
> @@ -143,7 +148,7 @@
> }
>
> static int
> -nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
> +nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
> {
> char *argv0;
> ulong flags;
> @@ -181,7 +186,7 @@
> b = Bopen(argv[0], OREAD|OCEXEC);
> if(b == nil)
> return 0;
> - cdroot |= nsfile(fn, b, rpc);
> + cdroot |= nsfile(fn, b, rpc, dfd);
> Bterm(b);
> }else if(strcmp(argv0, "clear") == 0 && argc == 0){
> rfork(RFCNAMEG);
> @@ -212,6 +217,14 @@
> }else if(strcmp(argv0, "cd") == 0 && argc == 1){
> if(chdir(argv[0]) == 0 && *argv[0] == '/')
> cdroot = 1;
> + }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
> + //We should not silently fail if we can not honor a permit/block
> + //due to the parent namespace missing #c/drivers.
> + if(dfd <= 0)
> + sysfatal("%s requested, but could not open #c/drivers", argv0);
> + for(i=0; i < argc; i++)
> + if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
> + fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
> }
> return cdroot;
> }
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-11 16:11 ` Stanley Lieber
@ 2022-05-12 4:29 ` Jacob Moody
0 siblings, 0 replies; 18+ messages in thread
From: Jacob Moody @ 2022-05-12 4:29 UTC (permalink / raw)
To: 9front
On 5/11/22 10:11, Stanley Lieber wrote:
> in some cases, would it make more sense to invoke auth/newns at the listener level, rather than every time rc-httpd is called?
>
Kind of, listen uses that provided namespace for announcing and listening
itself and also as a parent for all of the running non trusted services. I think
this makes the union of what devices you might need to be pretty large. It also makes
using the namespace file to carve limited views of the root itself a bit more
difficult.But making a new namespace per connection is also pretty crap.
How about:
/rc/bin/service/namespace.tcp80
with a default checked in file of
/rc/bin/service/namespace.!tcp80
That way we get a more specific namespace that gets
shared for all connections of one service. You then also
have the -n argument to listen itself for setting
a namespace shared by all services.
This gets it out of /lib, and we could get
rid of /cfg/$sysname/namespace.$service.
moody
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-12 3:18 ` ori
@ 2022-05-12 5:10 ` Jacob Moody
2022-05-12 14:21 ` ori
0 siblings, 1 reply; 18+ messages in thread
From: Jacob Moody @ 2022-05-12 5:10 UTC (permalink / raw)
To: 9front
On 5/11/22 21:18, ori@eigenstate.org wrote:
> Quoth Jacob Moody <moody@mail.posixcafe.org>:
>> Hello,
>>
>> Gave this another go: http://okturing.com/src/13592/body
>>
>> This patch includes new namepace file for rc-httpd
>> and changes the existing anonymous login namespace for
>> ftpd, both make use of the sandboxing features.
>
> Looking like it's getting useful :)
I'm glad it's starting to look that way
> comments inline.
>
>> thanks,
>> moody
>>
>> diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
>> --- a/lib/namespace
>> +++ b/lib/namespace
>> @@ -1,5 +1,6 @@
>> # root
>> mount -aC #s/boot /root $rootspec
>> +bind $rootdir'/rc' /rc
>> bind -a $rootdir /
>
> Wondering why this change was needed; did a local change
> leak in?
>
Yeah I dont love this, but there is some reason.
rc needs /rc/lib/rcmain and most scripts
expect to find themselves under /rc/bin. My goal
for the namespace for rc-httpd was to steal
what was just needed from the real root fs,
unmount the root fs, then block /srv to
make #s/boot inaccessible. So I figured
the easiest way to go about that was for
#/ to expost a rc dir. Or at least I couldn't
find a better way of bringing in /root/rc to /
without bringing in most of /root.
/root/rc needs explicitely bound over /
because the original bind of /root to / is done with MAFTER.
So #/'s empty dirs take priority. If it were to stay
I think the line should be moved down to where /$cputype/bin
is bound.
Doing the first /root to / bind with MBEFORE would also
fix this.
>>
>> # kernel devices
>> --- a/lib/namespace.ftp
>> +++ b/lib/namespace.ftp
>> @@ -8,5 +8,6 @@
>> # bind a personal incoming directory below incoming
>> bind -c /usr/none/incoming /usr/web/incoming/none
>>
>> +permit |MedIa/
>> # this cuts off everything not mounted below /usr/web
>> bind /usr/web /
>> --- /tmp/diff100000742872
>> +++ b/lib/namespace.rc-httpd
>> @@ -1,0 +1,21 @@
>> +mount -aC #s/boot /root $rootspec
>> +
>> +# kernel devices
>> +bind #c /dev
>> +bind #d /fd
>> +bind -c #e /env
>> +bind #p /proc
>> +bind -a #l /net
>> +bind -a #I /net
>
> duplicated line?
>
Upper case I and lower case L.
>> +
>> +bind /root/$cputype/bin /bin
>> +bind /root/rc /rc
>> +bind -a /rc/bin /bin
>> +
>> +permit Mcde|plI/
>> +
>> +. /root/cfg/$sysname/namespace.rc-httpd
>> +
>> +unmount /root
>> +block Mc
>> +unmount #c /dev
>> --- a/sys/man/3/cons
>> +++ b/sys/man/3/cons
>> @@ -90,10 +90,30 @@
>> .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
>> +A process can forfeit access to a driver, for itself and it's future children, through a write to
>> +.B drivers.
>
> Is this accurate? I'd expect the way this is implement to affect all procs
> in the current namespace, not just the current one and its children.
>
> Maybe: 'a process can eject a driver from
> the current namespace through a write to
> drivers'.
You are correct about the implementation, it is shared by Pgrp.
Your suggestion sounds good to me.
>> +A message is one of:
>> +.IP "block \f2drivers\fP"
>> +block access to the listed
>> +.I drivers.
>> +.IP "permit \f2drivers\fP"
>> +permit access to just the provided
>> +.I drivers.
>> +.PP
>> +Drivers are identified by their short hand, the first column returned on reads,
>> +without the leading sharp. The following blocks access to
>
> Clunky phrasing. Perhaps '\f2drivers\f2 is a string of driver characters'.
> May also need to mention the implicication of disallowing 'M'.
That makes more sense, and I agree we should mention the quirks of 'M'.
>> +.IR env (3)
>> +and
>> +.IR sd (3):
>> +.IP
>> +.EX
>> +block se
>> .EE
>> .PP
>> The
>> --- a/sys/man/6/namespace
>> +++ b/sys/man/6/namespace
>> @@ -59,6 +59,15 @@
>> .I new
>> is missing.
>> .TP
>> +.BI block \ drivers
>> +Removes access to the listed kernel
>> +.I drivers.
>> +Devices are identified by their sharp name.
>> +.TP
>> +.BI permit \ drivers
>> +Permit access to only the listed kernel
>> +.I drivers.
>> +.TP
>> .BR clear
>> Clear the name space with
>> .BR rfork(RFCNAMEG) .
>> @@ -80,4 +89,5 @@
>> .SH "SEE ALSO"
>> .IR bind (1),
>> .IR namespace (4),
>> -.IR init (8)
>> +.IR init (8),
>> +.IR cons (3)
>> --- a/sys/src/9/boot/boot.c
>> +++ b /sys/src/9/boot/boot.c
>> @@ -19,6 +19,7 @@
>> if(await(buf, sizeof(buf)) < 0)
>> goto Err;
>>
>> + bind("/root/rc", "/rc", MREPL);
>> bind(root, "/", MAFTER);
>
> Hm. as before, I don't understand why this bind is needed:
> binding /root to / should already lead to /rc being /root/rc.
>
See above.
>>
>> buf[0] = '/';
>> --- 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.
>> + */
>> + 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;
>> + } else
>> + devunmount = 0;
>> +
>> + /*
>> * 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 && !driversallowed(up->pgrp, r))
>> + error(Enoattach);
>> +
>> c = devtab[t]->attach(up->genbuf+n);
>> break;
>>
>> --- a/sys/src/9/port/devcons.c
>> +++ b/sys/src/9/port/devcons.c
>> @@ -39,6 +39,18 @@
>> CMrdb, "rdb", 0,
>> };
>>
>> +enum
>> +{
>> + CMblock,
>> + CMpermit,
>> +};
>> +
>> +Cmdtab drivermsg[] =
>> +{
>> + CMblock, "block", 0,
>> + CMpermit, "permit", 0,
>> +};
>> +
>> void
>> printinit(void)
>> {
>> @@ -332,7 +344,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 +595,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();
>> @@ -676,6 +694,26 @@
>> error(Eperm);
>> break;
>>
>> + case Qdrivers:
>> + cb = parsecmd(a, n);
>> +
>> + if(waserror()) {
>> + free(cb);
>> + nexterror();
>> + }
>> + ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
>> + switch(ct->index) {
>> + case CMblock:
>> + driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
>> + break;
>> + case CMpermit:
>> + driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
>> + break;
>> + }
>> + poperror();
>> + free(cb);
>> + break;
>> +
>> case Qreboot:
>> if(!iseve())
>> error(Eperm);
>> @@ -935,6 +973,64 @@
>> break;
>> }
>> return n+1;
>> +}
>> +
>> +void
>> +driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
>> +{
>> + 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(i=0; i < argc; i++)
>> + for(p = argv[i]; *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
>> +driversallowed(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 driversallowed(pgrp, 'M');
>> }
>>
>> void
>> --- a/sys/src/9/port/devroot.c
>> +++ b/sys/src/9/port/devroot.c
>> @@ -105,6 +105,7 @@
>> addrootdir("net");
>> addrootdir("net.alt");
>> addrootdir("proc");
>> + addrootdir("rc");
>> addrootdir("root");
>> addrootdir("srv");
>> addrootdir("shr");
>> --- 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 driverscmd(Pgrp*,int,int,char**);
>> +int driversallowed(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;
>> + driverscmd(up->pgrp, 1, 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;
>> + driverscmd(p->pgrp, 1, 1, &devs);
>>
>> if(flag & RFREND)
>> p->rgrp = newrgrp();
>> --- a/sys/src/libauth/newns.c
>> +++ b/sys/src/libauth/newns.c
>> @@ -14,8 +14,8 @@
>> static int setenv(char*, char*);
>> static char *expandarg(char*, char*);
>> static int splitargs(char*, char*[], char*, int);
>> -static int nsfile(char*, Biobuf *, AuthRpc *);
>> -static int nsop(char*, int, char*[], AuthRpc*);
>> +static int nsfile(char*, Biobuf *, AuthRpc *, int);
>> +static int nsop(char*, int, char*[], AuthRpc*, int);
>> static int catch(void*, char*);
>>
>> int newnsdebug;
>> @@ -35,7 +35,7 @@
>> {
>> Biobuf *b;
>> char home[4*ANAMELEN];
>> - int afd, cdroot;
>> + int afd, cdroot, dfd;
>> char *path;
>> AuthRpc *rpc;
>>
>> @@ -51,6 +51,10 @@
>> }
>> /* rpc != nil iff afd >= 0 */
>>
>> + dfd = open("#c/drivers", OWRITE|OCEXEC);
>> + if(dfd < 0 && newnsdebug)
>> + fprint(2, "open #c/drivers: %r\n");
>> +
>> if(file == nil){
>> if(!newns){
>> werrstr("no namespace file specified");
>> @@ -70,7 +74,8 @@
>> setenv("home", home);
>> }
>>
>> - cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
>> + cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
>> + close(dfd);
>> Bterm(b);
>> freecloserpc(rpc);
>>
>> @@ -87,7 +92,7 @@
>> }
>>
>> static int
>> -nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
>> +nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
>> {
>> int argc;
>> char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
>> @@ -103,7 +108,7 @@
>> continue;
>> argc = splitargs(cmd, argv, argbuf, NARG);
>> if(argc)
>> - cdroot |= nsop(fn, argc, argv, rpc);
>> + cdroot |= nsop(fn, argc, argv, rpc, dfd);
>> }
>> atnotify(catch, 0);
>> return cdroot;
>> @@ -143,7 +148,7 @@
>> }
>>
>> static int
>> -nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
>> +nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
>> {
>> char *argv0;
>> ulong flags;
>> @@ -181,7 +186,7 @@
>> b = Bopen(argv[0], OREAD|OCEXEC);
>> if(b == nil)
>> return 0;
>> - cdroot |= nsfile(fn, b, rpc);
>> + cdroot |= nsfile(fn, b, rpc, dfd);
>> Bterm(b);
>> }else if(strcmp(argv0, "clear") == 0 && argc == 0){
>> rfork(RFCNAMEG);
>> @@ -212,6 +217,14 @@
>> }else if(strcmp(argv0, "cd") == 0 && argc == 1){
>> if(chdir(argv[0]) == 0 && *argv[0] == '/')
>> cdroot = 1;
>> + }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
>> + //We should not silently fail if we can not honor a permit/block
>> + //due to the parent namespace missing #c/drivers.
>> + if(dfd <= 0)
>> + sysfatal("%s requested, but could not open #c/drivers", argv0);
>> + for(i=0; i < argc; i++)
>> + if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
>> + fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
>> }
>> return cdroot;
>> }
>>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-12 5:10 ` Jacob Moody
@ 2022-05-12 14:21 ` ori
2022-05-23 5:42 ` Jacob Moody
0 siblings, 1 reply; 18+ messages in thread
From: ori @ 2022-05-12 14:21 UTC (permalink / raw)
To: 9front
Quoth Jacob Moody <moody@mail.posixcafe.org>:
> >> diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
> >> --- a/lib/namespace
> >> +++ b/lib/namespace
> >> @@ -1,5 +1,6 @@
> >> # root
> >> mount -aC #s/boot /root $rootspec
> >> +bind $rootdir'/rc' /rc
> >> bind -a $rootdir /
> >
> > Wondering why this change was needed; did a local change
> > leak in?
> >
>
> Yeah I dont love this, but there is some reason.
>
> rc needs /rc/lib/rcmain
maybe we change that, and either make it optional or hard
code it into rc?
(does anyone customize it?)
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-12 14:21 ` ori
@ 2022-05-23 5:42 ` Jacob Moody
2022-05-23 17:06 ` cinap_lenrek
0 siblings, 1 reply; 18+ messages in thread
From: Jacob Moody @ 2022-05-23 5:42 UTC (permalink / raw)
To: 9front
Another go at this. Bit of a refactor of the
kernel changes and wrapped up the userspace work.
Since some of the thoughts are scattered throughout
the thread now I wanted to provide an overview of how things ended up:
A process can eject devices from it's namespace through
writes to /dev/drivers. Ejected devices can not be walked.
A 'permit' command is available to allow processes to eject
devices by whitelist rather then blacklist.
Support for these commands has been added to newns, allowing
namespace files to use eject/permit commands. These new commands
are used in /lib/namespace.ftp to restrict anonymous login.
aux/listen now allows individual namespace files to be used
per service. This namespace is sourced one per listener. Each
connection receives a copy of the namespace. A example
/rc/bin/service/!tcp80.namespace is provided which builds
an isolated webroot. In order to accomplish this without
bringing in most of /root, a /rc folder was added to #/.
This is prsented as one diff, but I plan to commit this
in chunks.
Thanks,
moody
---
diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted
--- a/lib/namespace
+++ b/lib/namespace
@@ -22,6 +22,7 @@
# standard bin
bind /$cputype/bin /bin
+bind $rootdir'/rc' /rc
bind -a /rc/bin /bin
# internal networks
--- a/lib/namespace.ftp
+++ b/lib/namespace.ftp
@@ -8,5 +8,6 @@
# bind a personal incoming directory below incoming
bind -c /usr/none/incoming /usr/web/incoming/none
+permit |MedIa/
# this cuts off everything not mounted below /usr/web
bind /usr/web /
--- /tmp/diff100000406351
+++ b/rc/bin/service/!tcp80.namespace
@@ -1,0 +1,24 @@
+mount -aC #s/boot /root $rootspec
+
+# kernel devices
+bind #c /dev
+bind #d /fd
+bind -c #e /env
+bind #p /proc
+bind -a #l /net
+bind -a #I /net
+
+bind /root/$cputype/bin /bin
+bind /root/rc /rc
+bind -a /rc/bin /bin
+
+permit Mcde|pslI/
+
+# grab just our webroot
+bind /root/usr/web /srv
+
+# or bind in the actual root
+# bind -a /root /
+
+unmount /root
+eject Ms
--- a/sys/man/3/cons
+++ b/sys/man/3/cons
@@ -90,10 +90,32 @@
.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
+A process can eject a driver from the current namespace through a write to
+.B drivers.
+A message is one of:
+.IP "eject \f2drivers\fP"
+block access to the listed
+.I drivers.
+.IP "permit \f2drivers\fP"
+permit access to only the provided
+.I drivers.
+.PP
+\f2Drivers\fP is a string of driver characters. Ejecting
+.IR mnt (3)
+prevents new mounts in to the current namespace.
+The following blocks access to
+.IR env (3)
+and
+.IR sd (3):
+.IP
+.EX
+eject se
.EE
.PP
The
--- a/sys/man/3/root
+++ b/sys/man/3/root
@@ -10,6 +10,7 @@
.B /net
.B /net.alt
.B /proc
+.B /rc
.B /root
.B /srv
.fi
--- a/sys/man/6/namespace
+++ b/sys/man/6/namespace
@@ -59,6 +59,17 @@
.I new
is missing.
.TP
+.BI eject \ drivers
+Ejects the listed kernel
+.I drivers
+from the namespace.
+.I Drivers
+is a string of driver characters.
+.TP
+.BI permit \ drivers
+Permit access to only the listed kernel
+.I drivers.
+.TP
.BR clear
Clear the name space with
.BR rfork(RFCNAMEG) .
@@ -80,4 +91,5 @@
.SH "SEE ALSO"
.IR bind (1),
.IR namespace (4),
-.IR init (8)
+.IR init (8),
+.IR cons (3)
--- a/sys/man/8/listen
+++ b/sys/man/8/listen
@@ -96,6 +96,14 @@
an inbound call on the TCP network for port 565 executes service
.BR tcp565 .
.PP
+Services may have individual
+.IR namespace (6)
+files specified within
+.IR srvdir .
+If provided, the namespace is used as the parent for each connection
+to the corresponding service. Namespace files are found by appending a .namespace
+suffix to the service name.
+.PP
At least the following services are available in
.BR /bin/service .
.TF \ tcp0000
--- a/sys/src/9/boot/boot.c
+++ b/sys/src/9/boot/boot.c
@@ -25,6 +25,7 @@
buf[1+read(open("/env/cputype", OREAD|OCEXEC), buf+1, sizeof buf - 6)] = '\0';
strcat(buf, bin);
bind(buf, bin, MAFTER);
+ bind("/root/rc", "/rc", MREPL);
bind("/rc/bin", bin, MAFTER);
exec("/bin/bootrc", argv);
--- 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
+deveject(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,18 @@
CMrdb, "rdb", 0,
};
+enum
+{
+ CMeject,
+ CMpermit,
+};
+
+Cmdtab drivermsg[] =
+{
+ CMeject, "eject", 0,
+ CMpermit, "permit", 0,
+};
+
void
printinit(void)
{
@@ -332,7 +344,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 +595,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();
@@ -622,7 +640,7 @@
long l, bp;
char *a;
Mach *mp;
- int id;
+ int id, i, invert;
ulong offset;
Cmdbuf *cb;
Cmdtab *ct;
@@ -674,6 +692,32 @@
case Qconfig:
error(Eperm);
+ break;
+
+ case Qdrivers:
+ cb = parsecmd(a, n);
+
+ if(waserror()) {
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
+ invert = 0;
+ switch(ct->index) {
+ case CMeject:
+ invert = 0;
+ break;
+ case CMpermit:
+ invert = 1;
+ break;
+ default:
+ error(Ebadarg);
+ break;
+ }
+ for(i = 1; i < cb->nf; i++)
+ deveject(up->pgrp, invert, cb->f[i]);
+ poperror();
+ free(cb);
break;
case Qreboot:
--- a/sys/src/9/port/devroot.c
+++ b/sys/src/9/port/devroot.c
@@ -105,6 +105,7 @@
addrootdir("net");
addrootdir("net.alt");
addrootdir("proc");
+ addrootdir("rc");
addrootdir("root");
addrootdir("srv");
addrootdir("shr");
--- 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 deveject(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;
+ deveject(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;
+ deveject(p->pgrp, 1, devs);
if(flag & RFREND)
p->rgrp = newrgrp();
--- a/sys/src/cmd/aux/listen.c
+++ b/sys/src/cmd/aux/listen.c
@@ -136,6 +136,7 @@
{
int ctl, pid, start;
char dir[40], err[128], ds[128];
+ char prog[Maxpath], serv[Maxserv], ns[Maxpath];
long childs;
Announce *a;
Waitmsg *wm;
@@ -178,6 +179,10 @@
sleep((pid*10)%200);
snprint(ds, sizeof ds, "%s!%s!%s", protodir, addr, a->a);
+ snprint(serv, sizeof serv, "%s%s", proto, a->a);
+ snprint(prog, sizeof prog, "%s/%s", srvdir, serv);
+ snprint(ns, sizeof ns, "%s.namespace", prog);
+
whined = a->whined;
/* a process per service */
@@ -201,7 +206,11 @@
else
exits("ctl");
}
- dolisten(dir, ctl, srvdir, a->a, &childs);
+ procsetname("%s %s", dir, ds);
+ if(!trusted)
+ if(newns("none", ns) < 0)
+ syslog(0, listenlog, "can't build namespace %s: %r\n", ns);
+ dolisten(dir, ctl, serv, prog, &childs);
close(ctl);
}
default:
@@ -299,6 +308,8 @@
continue;
if(strncmp(nm, proto, nlen) != 0)
continue;
+ if(strstr(nm + nlen, ".namespace") != nil)
+ continue;
addannounce(nm + nlen);
}
free(db);
@@ -329,15 +340,10 @@
}
void
-dolisten(char *dir, int ctl, char *srvdir, char *port, long *pchilds)
+dolisten(char *dir, int ctl, char *serv, char *prog, long *pchilds)
{
char ndir[40], wbuf[64];
- char prog[Maxpath], serv[Maxserv];
int nctl, data, wfd, nowait;
-
- procsetname("%s %s!%s!%s", dir, proto, addr, port);
- snprint(serv, sizeof serv, "%s%s", proto, port);
- snprint(prog, sizeof prog, "%s/%s", srvdir, serv);
wfd = -1;
nowait = RFNOWAIT;
--- a/sys/src/libauth/newns.c
+++ b/sys/src/libauth/newns.c
@@ -14,8 +14,8 @@
static int setenv(char*, char*);
static char *expandarg(char*, char*);
static int splitargs(char*, char*[], char*, int);
-static int nsfile(char*, Biobuf *, AuthRpc *);
-static int nsop(char*, int, char*[], AuthRpc*);
+static int nsfile(char*, Biobuf *, AuthRpc *, int);
+static int nsop(char*, int, char*[], AuthRpc*, int);
static int catch(void*, char*);
int newnsdebug;
@@ -35,7 +35,7 @@
{
Biobuf *b;
char home[4*ANAMELEN];
- int afd, cdroot;
+ int afd, cdroot, dfd;
char *path;
AuthRpc *rpc;
@@ -51,6 +51,10 @@
}
/* rpc != nil iff afd >= 0 */
+ dfd = open("#c/drivers", OWRITE|OCEXEC);
+ if(dfd < 0 && newnsdebug)
+ fprint(2, "open #c/drivers: %r\n");
+
if(file == nil){
if(!newns){
werrstr("no namespace file specified");
@@ -70,7 +74,8 @@
setenv("home", home);
}
- cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
+ cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
+ close(dfd);
Bterm(b);
freecloserpc(rpc);
@@ -87,7 +92,7 @@
}
static int
-nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
+nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
{
int argc;
char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
@@ -103,7 +108,7 @@
continue;
argc = splitargs(cmd, argv, argbuf, NARG);
if(argc)
- cdroot |= nsop(fn, argc, argv, rpc);
+ cdroot |= nsop(fn, argc, argv, rpc, dfd);
}
atnotify(catch, 0);
return cdroot;
@@ -143,7 +148,7 @@
}
static int
-nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
+nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
{
char *argv0;
ulong flags;
@@ -181,7 +186,7 @@
b = Bopen(argv[0], OREAD|OCEXEC);
if(b == nil)
return 0;
- cdroot |= nsfile(fn, b, rpc);
+ cdroot |= nsfile(fn, b, rpc, dfd);
Bterm(b);
}else if(strcmp(argv0, "clear") == 0 && argc == 0){
rfork(RFCNAMEG);
@@ -212,6 +217,14 @@
}else if(strcmp(argv0, "cd") == 0 && argc == 1){
if(chdir(argv[0]) == 0 && *argv[0] == '/')
cdroot = 1;
+ }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "eject") == 0)){
+ //We should not silently fail if we can not honor a permit/eject
+ //due to the parent namespace missing #c/drivers.
+ if(dfd <= 0)
+ sysfatal("%s requested, but could not open #c/drivers", argv0);
+ for(i=0; i < argc; i++)
+ if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
+ fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
}
return cdroot;
}
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-23 5:42 ` Jacob Moody
@ 2022-05-23 17:06 ` cinap_lenrek
2022-05-23 17:37 ` Jacob Moody
0 siblings, 1 reply; 18+ messages in thread
From: cinap_lenrek @ 2022-05-23 17:06 UTC (permalink / raw)
To: 9front
try to preserve that the ns file looks like shell script.
avoid "eject", as that is already a existing command that does
something completely different.
i dont like "permit" as it is too generic and not clear what it
operates on.
in any case, think of these operations as something that we should
also provide a shell wrapper for and it lives in the /bin namespace.
maybe should be a single command limilar to chmod/chgrp but operating
on the kernel device map?
like:
devmask -X # "eject" X device
devmask ABCDEF # set ABCDEF as allowed
any better ideas?
--
cinap
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [9front] [PATCH] Unmount to remove sharp devices.
2022-05-23 17:06 ` cinap_lenrek
@ 2022-05-23 17:37 ` Jacob Moody
0 siblings, 0 replies; 18+ messages in thread
From: Jacob Moody @ 2022-05-23 17:37 UTC (permalink / raw)
To: 9front
On 5/23/22 11:06, cinap_lenrek@felloff.net wrote:
> try to preserve that the ns file looks like shell script.
>
> avoid "eject", as that is already a existing command that does
> something completely different.
>
> i dont like "permit" as it is too generic and not clear what it
> operates on.
>
> in any case, think of these operations as something that we should
> also provide a shell wrapper for and it lives in the /bin namespace.
>
> maybe should be a single command limilar to chmod/chgrp but operating
> on the kernel device map?
>
> like:
>
> devmask -X # "eject" X device
> devmask ABCDEF # set ABCDEF as allowed
>
> any better ideas?
This all makes sense.
There was some followup discussion for this on irc that I want to
make sure is captured here:
The command written to /dev/drivers will change to be one of:
chdev DEVS
chdev -DEVS
with them acting like the current 'permit' and 'eject' commands respectively.
This will be given a matching chdev(1) rc script, and the ns file command
will be updated to match the chdev(1) interface.
thanks,
moody
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2022-05-23 17:39 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-04 14:09 [9front] [PATCH] Unmount to remove sharp devices Jacob Moody
2022-05-04 15:05 ` ori
2022-05-04 15:31 ` ori
2022-05-04 16:15 ` Stanley Lieber
2022-05-04 17:41 ` Lyndon Nerenberg (VE7TFX/VE6BBM)
2022-05-04 17:55 ` Jacob Moody
2022-05-05 1:59 ` Alex Musolino
2022-05-05 16:07 ` Jacob Moody
2022-05-08 2:55 ` Jacob Moody
2022-05-11 14:47 ` Jacob Moody
2022-05-11 16:11 ` Stanley Lieber
2022-05-12 4:29 ` Jacob Moody
2022-05-12 3:18 ` ori
2022-05-12 5:10 ` Jacob Moody
2022-05-12 14:21 ` ori
2022-05-23 5:42 ` Jacob Moody
2022-05-23 17:06 ` cinap_lenrek
2022-05-23 17:37 ` Jacob Moody
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).