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.c223
1 files changed, 175 insertions, 48 deletions
diff --git a/fw/src/netfs.c b/fw/src/netfs.c
index f28485f..4aa1909 100644
--- a/fw/src/netfs.c
+++ b/fw/src/netfs.c
@@ -95,65 +95,123 @@ childpath(char *dir, char *name)
return smprint("%s/%s", dir, name);
}
-/* does path name this top-level entry, or something under it? */
-static int
-under(char *path, char *name)
-{
- int n;
-
- n = strlen(name);
- return strncmp(path, name, n) == 0 && (path[n] == '\0' || path[n] == '/');
-}
+/*
+ * What is served, named; not what is hidden, listed.
+ *
+ * Listing what to hide has now been wrong twice. "trans" installs
+ * kernel address translations and devip gates it with iseve(), which is
+ * fw's identity here and not the caller's, so a filtered program could
+ * write it and reach the whole machine. "log" is a trace of every
+ * connection on the machine, and only its write was refused, when
+ * reading it was the leak. Neither was a hard problem: both were
+ * simply missing from a list of things to deny.
+ *
+ * So it is the other way round. Nothing is served unless it is named
+ * below, and the next file devip grows is invisible until someone
+ * decides otherwise - which is the direction a firewall should fail in,
+ * and the same argument the rule parser makes about attributes it does
+ * not recognise.
+ */
/*
- * Raw packet access. Kept out of the served tree entirely rather than
- * made unopenable, so that a program probing for a way out does not
- * even see one.
+ * devip's protocol directories, by name. Not "any directory with a
+ * clone file": devether has one of those too, and #l bound into /net
+ * would have become a protocol. A protocol missing from this list is
+ * one nobody can reach, which is the safe way to be out of date.
+ * ipmux is left out on purpose - it is raw packet access.
*/
+static char *protos[] = {
+ "tcp", "udp", "il", "icmp", "icmpv6", "rudp", "gre", "esp",
+ "ipifc", /* read-only: see writable() */
+ nil,
+};
+
+/* the files a conversation directory has */
+static char *convfiles[] = {
+ "ctl", "data", "err", "listen", "local", "remote", "status",
+ nil,
+};
+
+/* of those, the ones a filtered program may write */
+static char *rwconvfiles[] = {
+ "ctl", "data", "err", "listen",
+ nil,
+};
+
+/* everything else served from the root, and whether writing it is allowed */
+static struct {
+ char *name;
+ int rw;
+} rootfiles[] = {
+ { "cs", 1 }, /* asking for a translation is a write */
+ { "dns", 1 },
+ { "arp", 0 },
+ { "bootp", 0 },
+ { "iproute", 0 },
+ { "ipselftab", 0 },
+ { "ndb", 0 },
+};
+
static int
-hidden(char *path)
+inlist(char **l, char *s)
{
- if(strncmp(path, "ether", 5) == 0 && path[5] >= '0' && path[5] <= '9')
- return 1;
- return under(path, "ipmux");
+ int i;
+
+ for(i = 0; l[i] != nil; i++)
+ if(strcmp(l[i], s) == 0)
+ return 1;
+ return 0;
}
-static char*
-protect(char *path, int mode)
+static int
+rootfile(char *name)
{
- char *p;
+ int i;
- if(hidden(path))
- return "fw: does not exist";
- if(strcmp(path, "ipifc/clone") == 0)
- return "fw: interface creation denied";
+ for(i = 0; i < nelem(rootfiles); i++)
+ if(strcmp(rootfiles[i].name, name) == 0)
+ return i;
+ return -1;
+}
- /*
- * 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";
+static int
+isnum(char *s)
+{
+ if(*s == '\0')
+ return 0;
+ for(; *s != '\0'; s++)
+ if(*s < '0' || *s > '9')
+ return 0;
+ return 1;
+}
- /*
- * 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;
+/*
+ * Split a path into its components, of which a served path has at most
+ * three. A deeper path, an over-long one, or one with an empty
+ * component is not a path this server ever handed out, so it is not one
+ * it will honour: that also disposes of names like "tcp/../.." arriving
+ * as a single walk element from a client speaking 9P directly.
+ */
+static int
+splitpath(char *path, char *buf, int nbuf, char **f)
+{
+ int i, n;
+
+ if(strlen(path) >= nbuf)
+ return -1;
+ strcpy(buf, path);
+ n = getfields(buf, f, 4, 0, "/");
+ if(n < 1 || n > 3)
+ return -1;
+ for(i = 0; i < n; i++)
+ if(*f[i] == '\0')
+ return -1;
+ return n;
}
/*
- * A directory in the root is a protocol directory if it has a clone
- * file. Asking the filesystem beats hardcoding a list that goes stale.
+ * A protocol this kernel actually has. The name list says which ones
+ * may be served; the clone file says which ones are there.
*/
static int
isproto(char *name)
@@ -161,7 +219,7 @@ isproto(char *name)
char *p;
int ok;
- if(strchr(name, '/') != nil || *name == '\0')
+ if(!inlist(protos, name))
return 0;
p = smprint("%s/%s/clone", orig, name);
ok = access(p, AEXIST) == 0;
@@ -169,6 +227,75 @@ isproto(char *name)
return ok;
}
+static int
+served(char *path)
+{
+ char buf[128], *f[4];
+ int n;
+
+ if(*path == '\0')
+ return 1; /* the root */
+ if((n = splitpath(path, buf, sizeof buf, f)) < 0)
+ return 0;
+
+ if(!isproto(f[0]))
+ return n == 1 && rootfile(f[0]) >= 0;
+ if(n == 1)
+ return 1; /* the protocol directory */
+
+ if(isnum(f[1])){
+ if(n == 2)
+ return 1; /* a conversation directory */
+ /*
+ * An interface's data and snoop files are a wire. On a
+ * machine also running fw -e one of them is *the* wire, so
+ * either hands a filtered program the packet stream the
+ * filter exists to control.
+ */
+ if(strcmp(f[0], "ipifc") == 0)
+ if(strcmp(f[2], "data") == 0 || strcmp(f[2], "snoop") == 0)
+ return 0;
+ return inlist(convfiles, f[2]);
+ }
+ if(n != 2)
+ return 0;
+ if(strcmp(f[0], "ipifc") == 0)
+ return strcmp(f[1], "stats") == 0; /* no new interfaces */
+ return strcmp(f[1], "clone") == 0 || strcmp(f[1], "stats") == 0;
+}
+
+static int
+writable(char *path)
+{
+ char buf[128], *f[4];
+ int n, i;
+
+ if((n = splitpath(path, buf, sizeof buf, f)) < 0)
+ return 0;
+ if(!isproto(f[0])){
+ if(n != 1 || (i = rootfile(f[0])) < 0)
+ return 0;
+ return rootfiles[i].rw;
+ }
+ if(strcmp(f[0], "ipifc") == 0)
+ return 0; /* the interfaces are not ours to change */
+ if(n == 2)
+ return strcmp(f[1], "clone") == 0;
+ if(n == 3 && isnum(f[1]))
+ return inlist(rwconvfiles, f[2]);
+ return 0;
+}
+
+static char*
+protect(char *path, int mode)
+{
+ if(!served(path))
+ return "fw: does not exist";
+ if((mode & 3) != OREAD && !writable(path))
+ return "fw: read-only under fw";
+ return nil;
+}
+
/*
* If path is a protocol ctl file - "tcp/clone" or "tcp/1/ctl" - return
* the protocol name. Opening clone yields an fd that is itself the new
@@ -341,7 +468,7 @@ slurpdir(Fnode *f, char *rp)
sz = 0;
for(i = 0; i < n; i++){
cp = childpath(f->path, d[i].name);
- if(hidden(cp)){
+ if(!served(cp)){
free(cp);
continue;
}
@@ -428,7 +555,7 @@ fswalk1(Fid *fid, char *name, Qid *q)
f = fid->aux;
np = childpath(f->path, name);
- if(hidden(np)){
+ if(!served(np)){
free(np);
return "fw: does not exist";
}