summaryrefslogtreecommitdiff
path: root/fw/src/netfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'fw/src/netfs.c')
-rw-r--r--fw/src/netfs.c67
1 files changed, 63 insertions, 4 deletions
diff --git a/fw/src/netfs.c b/fw/src/netfs.c
index c87412d..f28485f 100644
--- a/fw/src/netfs.c
+++ b/fw/src/netfs.c
@@ -121,11 +121,31 @@ hidden(char *path)
static char*
protect(char *path, int mode)
{
+ char *p;
+
if(hidden(path))
return "fw: does not exist";
if(strcmp(path, "ipifc/clone") == 0)
return "fw: interface creation denied";
- if(under(path, "ipifc") || under(path, "iproute") || under(path, "arp"))
+
+ /*
+ * An interface's data file is a wire. On a machine that is also
+ * running fw -e it is *the* wire, so reading it hands a filtered
+ * program the packet stream the filter exists to control. Not
+ * read-only: not at all.
+ */
+ if(under(path, "ipifc"))
+ if((p = strrchr(path, '/')) != nil && strcmp(p+1, "data") == 0)
+ return "fw: denied";
+
+ /*
+ * ndb is the machine's configuration and log is a trace of every
+ * connection on it, both mode 0666. Writing the first
+ * reconfigures name service for everyone; reading the second
+ * after enabling it watches the whole machine.
+ */
+ if(under(path, "ipifc") || under(path, "iproute") || under(path, "arp")
+ || under(path, "ndb") || under(path, "log"))
if((mode & 3) != OREAD)
return "fw: read-only under fw";
return nil;
@@ -181,6 +201,22 @@ ctlproto(char *path)
* connect takes addr!port with optional trailing fields; announce takes
* a bare port, or addr!port with addr often "*".
*/
+/*
+ * Control messages that only change how this one conversation behaves,
+ * and so cannot reach the network by themselves. Everything outside
+ * this list is refused: see checkctl.
+ */
+static char *okverbs[] = {
+ "bind", /* the local address; announce is what opens */
+ "ttl",
+ "tos",
+ "ignoreadvice",
+ "close",
+ "hangup",
+ "keepalive",
+ nil,
+};
+
static char*
checkctl(char *proto, char *msg, long n)
{
@@ -188,7 +224,7 @@ checkctl(char *proto, char *msg, long n)
static char err[128];
uchar ip[IPaddrlen], mask[IPaddrlen];
Rule *rule;
- int nf, na, verb, anyip, port, lport;
+ int nf, na, verb, anyip, port, lport, i;
if(n <= 0)
return nil;
@@ -203,8 +239,31 @@ checkctl(char *proto, char *msg, long n)
verb = Vout;
else if(strcmp(f[0], "announce") == 0)
verb = Vin;
- else
- return nil; /* hangup, ttl, keepalive: not policy */
+ else{
+ /*
+ * Anything else is refused unless it is known to be
+ * harmless. Letting unknown control messages through was
+ * a hole, not a convenience: "headers" on a udp
+ * conversation turns it into one that carries its own
+ * destination, and the write queue is live from the
+ * moment it is cloned, so three writes send a datagram
+ * anywhere with no connect for a rule to match. gre has
+ * "raw" and "forward"; rudp and icmpv6 have "headers"
+ * too.
+ *
+ * This is the same argument the rule parser already makes
+ * about attributes it does not recognise, applied to the
+ * place it was not.
+ */
+ for(i = 0; okverbs[i] != nil; i++)
+ if(strcmp(f[0], okverbs[i]) == 0)
+ return nil;
+ syslog(0, "fw", "deny %s %s: control message not permitted",
+ proto, f[0]);
+ snprint(err, sizeof err,
+ "fw: %s: not a permitted control message", f[0]);
+ return err;
+ }
if(nf < 2)
return nil; /* malformed; let the kernel say so */