diff options
Diffstat (limited to 'fw/src/netfs.c')
| -rw-r--r-- | fw/src/netfs.c | 67 |
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 */ |
