|
Three findings, one interface. matchrule handed back a Rule* and a
pointer into a static char[128], both read by the caller after it had
released rulelock:
e = matchrule(verb, ..., &rule);
if(rule != nil && rule->log) /* freed? */
syslog(0, "fw", "... %s", e); /* whose? */
A rule set installed between the return and those two lines frees the
Rule under them, which is a narrow window but this is a firewall, and
two procs deciding at once overwrite each other's reason -- in a program
whose entire output is the reason. netfs.c ran multi-proc from the
first blocking open and had its own static err with the same problem.
Neither is a race you can test for; both stop existing if the answer
lives in the caller's frame, so it does. Seven positional arguments
become named fields while the signature is being rewritten anyway.
The third is that revalidate could not ask without being counted. A
rule edit rebuilds the set, so every hit count starts at zero, and then
revalidate re-checks each live flow against the new rules and charged
every one of them to the rule that matched. So a rule that had decided
nothing since the edit reported one decision per live connection, and
stats answered a different question after every edit. count says
whether this is traffic.
Also: the log said "deny tcp connect 127.0.0.2" for a connection to a
port it never named. getfields writes over the separators it splits
on, so f[1] afterwards is only what precedes the first "!". The
address is copied before it is taken apart.
Four checks. Two exercise the log path end to end, denied and
permitted, matching the full address and the rule number in
/sys/log/fw -- which they create if it is missing and remove again if
they made it. One reads the hit count after an edit: with count put
back to 1 in revalidate it reports 1 where 0 is right. 51 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
One program with three modes, sharing one rule engine and one ndb rule
language. Which mode it is depends on what you point it at, and it says
so at startup rather than choosing silently.
fw -e /net/ether0 rules.ndb a card: every packet in or out
fw rules.ndb <side> <side> two networks: everything crossing
fw rules.ndb one namespace: what programs ask for
The first two filter packets on a wire, using the pkt medium: the stack
gives up its card and gets a synthetic one with fw on the other end, so
nothing reaches it that fw did not pass. Since the stack no longer has
ethernet, fw answers ARP for the address it stands in for.
The third serves a filtered /net and matches connect and announce before
they reach the kernel, so a refusal comes back out of dial(2) with a
reason. That is only a boundary if the program also loses #I, which
/dev/drivers does and cannot be undone; fw.rc does it in the right order.
Rules are ndb, matched top to bottom, first match wins, no match denies.
Connections are tracked, so permitting traffic one way permits the
replies. A rule change drops connections the new rules forbid rather
than letting them finish: a block blocks. Logging is per rule, to
/sys/log/fw.
Tested on the init-test VM in all three modes: a page fetched through a
real card, a TCP handshake across two networks, request filtering with
the escape routes closed, live rule changes killing established
connections, and one rule file working unchanged at both altitudes.
doc/todo.md has what is not done. Item 1 is the one that matters: a fw
that dies takes the card's address with it, so the machine loses its
network and fw cannot restart unaided. That also blocks svc supervision.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|