diff options
| author | Calvin Morrison <calvin@pobox.com> | 2026-08-18 23:14:48 -0400 |
|---|---|---|
| committer | Calvin Morrison <calvin@pobox.com> | 2026-08-18 23:14:48 -0400 |
| commit | e03c1bb3df2e70f5fb707f5c7a7081022ed31d8c (patch) | |
| tree | 1a93653efacc9077a26dfcf50bdc8147ccf5dacf /fw/src | |
| parent | 38f289588d7f17f86a20a3372390257e3214155b (diff) | |
netfs: check the caller when there is one to check
An announce names no peer -- at that moment nobody has called -- so
matchrule skipped every rule naming an address, and "deny=in
ip=1.2.3.4" did nothing at all in namespace mode while doing something
real at the packet layer. The code said so and called it right:
A rule naming a peer therefore cannot apply to an announce,
which is right: at this point there is no peer to name.
Right about the announce, wrong about the connection. A rule that
silently does nothing is the failure this program refuses to accept
from a mistyped attribute -- fw will not start rather than run with
"prot=tcp" ignored -- and it should not accept it from itself.
So the peer is asked about at listen time, when there is one. The fd
that listen yields is the new conversation's ctl file; its number reads
out of it at offset 0, so the program's own read, the one listen(2)
makes to learn the same number, still sees it. remote and local give
the peer and the port announced. If the rules refuse, the connection
is hung up and the open fails, and the program never has it.
The handshake has already happened by then: the kernel answered before
listen returned, and no filter at this altitude can prevent that. That
is the difference between a rule that is late and a rule that is
decorative, and it is worth the distinction.
Two checks, on the same pair of rule sets, differing only in whether
the caller is refused; both fail against the previous netfs.c. They
read /sys/log/fw as a difference rather than a total: the caller's port
is ephemeral, so nothing in the line belongs to this run, and the log
keeps what earlier runs put there. The machine's own address stands in
for a peer, since this one has no loopback configured -- announcing
127.0.0.1 gets "not a local IP address", and announcing a bare port
binds to :: and never sees a v4 call at all.
66 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'fw/src')
| -rw-r--r-- | fw/src/netfs.c | 121 |
1 files changed, 120 insertions, 1 deletions
diff --git a/fw/src/netfs.c b/fw/src/netfs.c index 1300717..f60d5bc 100644 --- a/fw/src/netfs.c +++ b/fw/src/netfs.c @@ -341,6 +341,113 @@ static char *okverbs[] = { }; /* + * If path is a protocol listen file - "tcp/1/listen" - return the + * protocol name. Opening one blocks until somebody connects, and the + * fd it yields is the ctl file of the connection that arrived. + */ +static char* +listenproto(char *path) +{ + char buf[64], *p, *q; + int n; + + if((p = strchr(path, '/')) == nil) + return nil; + n = p - path; + if(n <= 0 || n >= sizeof buf) + return nil; + memmove(buf, path, n); + buf[n] = '\0'; + if((q = strchr(p+1, '/')) == nil || strcmp(q+1, "listen") != 0) + return nil; + if(!isproto(buf)) + return nil; + return estrdup(buf); +} + +/* the address side of "10.9.9.1!1234", read out of local or remote */ +static int +convaddr(char *proto, int conv, char *which, uchar *ip, int *port) +{ + char path[256], buf[128], *p; + int fd, n; + + snprint(path, sizeof path, "%s/%s/%d/%s", orig, proto, conv, which); + if((fd = open(path, OREAD)) < 0) + return -1; + n = read(fd, buf, sizeof buf - 1); + close(fd); + if(n <= 0) + return -1; + buf[n] = '\0'; + if((p = strchr(buf, '\n')) != nil) + *p = '\0'; + if((p = strrchr(buf, '!')) == nil) + return -1; + *p++ = '\0'; + *port = atoi(p); + return parseip(ip, buf); +} + +/* + * A listen has returned a connection. Ask about the peer, now that + * there is one. + * + * The announce could not be matched against a peer - at that moment + * nobody had called - so any rule naming an address was skipped, and + * "deny=in ip=..." meant nothing here while meaning something at the + * packet layer. A rule that silently does nothing is the failure this + * program refuses to accept from a mistyped attribute, and it should + * not accept it from itself. + * + * The handshake has already happened: the kernel answered before listen + * returned, and no filter at this altitude can prevent that. What it + * can do is refuse the connection to the program and hang it up, which + * is the difference between a rule that is late and a rule that is + * decorative. + */ +static char* +checklisten(Match *m, char *proto, int fd) +{ + uchar ip[IPaddrlen], lip[IPaddrlen]; + char buf[64]; + int n, conv, lport; + + /* + * fd is the new conversation's ctl file and its number is what + * reading it gives. At offset 0, so the program's own read - the + * one listen(2) makes to learn the same number - still sees it. + */ + if((n = pread(fd, buf, sizeof buf - 1, 0)) <= 0) + return nil; + buf[n] = '\0'; + conv = atoi(buf); + + memset(m, 0, sizeof *m); + m->count = 1; + m->verb = Vin; + m->proto = proto; + m->ip = ip; + m->port = -1; + m->lport = -1; + if(convaddr(proto, conv, "remote", ip, &m->port) < 0) + return nil; /* gone already; let it be */ + if(convaddr(proto, conv, "local", lip, &lport) >= 0) + m->lport = lport; /* the port announced, for an lport rule */ + + if(matchrule(m)){ + if(m->log) + syslog(0, "fw", "allow %s listen %I!%d", proto, ip, m->port); + return nil; + } + syslog(0, "fw", "deny %s listen %I!%d: %s", proto, ip, m->port, m->err); + fprint(fd, "hangup"); + snprint(buf, sizeof buf, "%s", m->err); + snprint(m->err, sizeof m->err, "fw: %s", buf); + return m->err; +} + +/* * connect takes addr!port with optional trailing fields; announce takes * a bare port, or addr!port with addr often "*". */ @@ -590,7 +697,7 @@ static void fsopen(Req *r) { Fnode *f; - char *rp, *e; + char *rp, *e, *proto; int mode; f = r->fid->aux; @@ -615,6 +722,18 @@ fsopen(Req *r) responderror(r); return; } + if((proto = listenproto(f->path)) != nil){ + Match m; + + e = checklisten(&m, proto, f->fd); + free(proto); + if(e != nil){ + close(f->fd); + f->fd = -1; + respond(r, e); /* m outlives the respond, which packs it */ + return; + } + } respond(r, nil); } |
