summaryrefslogtreecommitdiff
path: root/fw/src/fw.c
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2026-08-18 22:05:20 -0400
committerCalvin Morrison <calvin@pobox.com>2026-08-18 22:05:20 -0400
commit2f408deee4d88ebd2ee87bbf5fe227ff764e0e6d (patch)
treee347c53f3c945dfd1ae9ea741223e4c658246347 /fw/src/fw.c
parent330c2a27a998a2a0d8b31af10edf087558ea7050 (diff)
rules: a rule set is not 64 kilobytes long
fmtrules formatted into a 64K buffer with seprint, which clamps, and returned how much it had written. Nothing looked at whether that was everything. Since prepend, append and delete all work by formatting the whole set out, editing the text and parsing it back -- deliberately, so that a rule typed at ctl and a rule in a file go through one parser -- editing a set past the limit did not truncate the display, it truncated the rules. Measured with 2000 rules, about 104K formatted: and all of it comes back want: 2000 got: 1214 and survives an edit want: ok got: refused with nothing lost off the end got: 1214 786 rules gone from the running firewall, and the only sign is that the edit after it failed. save wrote the same short file, so reload would then have made the loss permanent. Now sized and allocated to fit. The bound is per rule -- the fixed attributes at their longest, plus the protocol, which is the only part whose length is ndb's choice rather than ours -- summed under the same lock that formats, so an install cannot get between the two passes. flows had the identical cap and gets the identical fix; on a busy firewall it is the file most likely to reach it. Rulebuf is gone. Six checks: a 2000-rule set loads, comes back whole, survives an edit, and saves whole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'fw/src/fw.c')
-rw-r--r--fw/src/fw.c49
1 files changed, 27 insertions, 22 deletions
diff --git a/fw/src/fw.c b/fw/src/fw.c
index 3c13182..ef4aff0 100644
--- a/fw/src/fw.c
+++ b/fw/src/fw.c
@@ -508,8 +508,6 @@ static char *ctltext =
"changes take effect at once, and live connections that the\n"
"new rules forbid are dropped rather than left running.\n";
-enum { Rulebuf = 64*1024 };
-
/*
* ctl edits are done by writing the rule set back out as ndb, editing
* the text, and parsing the whole thing again. It is not the quickest
@@ -562,12 +560,11 @@ editrules(char *add, int atfront, int delete)
char *cur, *all, *err, *p, *nl;
int i;
- if((cur = mallocz(Rulebuf, 1)) == nil)
+ if((cur = rulestext()) == nil)
return "out of memory";
- cur[fmtrules(cur, Rulebuf-1)] = '\0';
if(delete > 0){
- /* fmtrules writes one line per rule, so line n is rule n */
+ /* rulestext writes one line per rule, so line n is rule n */
p = cur;
for(i = 1; i < delete && p != nil; i++)
if((p = strchr(p, '\n')) != nil)
@@ -618,9 +615,9 @@ saverules(char *file)
if(file == nil)
return "no rule file to save to";
- if((buf = mallocz(Rulebuf, 1)) == nil)
+ if((buf = rulestext()) == nil)
return "out of memory";
- n = fmtrules(buf, Rulebuf-1);
+ n = strlen(buf);
if((fd = create(file, OWRITE, 0644)) < 0){
free(buf);
return "cannot create the rule file";
@@ -653,20 +650,27 @@ flushflows(void)
return nil;
}
+/* allocated to fit, for the reason rulestext is */
static char*
flowtext(void)
{
char *buf, *p, *e;
Flow *f;
- long now;
+ long now, sz;
int i;
- if((buf = mallocz(Rulebuf, 1)) == nil)
- return nil;
- p = buf;
- e = buf + Rulebuf;
now = time(0);
lock(&flowlock);
+ sz = 1;
+ for(i = 0; i < Nflow; i++)
+ for(f = flowtab[i]; f != nil; f = f->next)
+ sz += 160;
+ if((buf = malloc(sz)) == nil){
+ unlock(&flowlock);
+ return nil;
+ }
+ p = buf;
+ e = buf + sz;
for(i = 0; i < Nflow; i++)
for(f = flowtab[i]; f != nil; f = f->next)
p = seprint(p, e, "%s %s %I!%d -> %I!%d idle %ld\n",
@@ -738,20 +742,17 @@ fsdestroyfid(Fid *fid)
static void
fsread(Req *r)
{
- char *s;
- long n;
+ char *s, *t;
switch((int)(uintptr)r->fid->file->aux){
case Qctl:
readstr(r, ctltext);
break;
case Qrules:
- if((s = mallocz(Rulebuf, 1)) == nil){
+ if((s = rulestext()) == nil){
respond(r, "out of memory");
return;
}
- n = fmtrules(s, Rulebuf-1);
- s[n] = '\0';
readstr(r, s);
free(s);
break;
@@ -764,14 +765,18 @@ fsread(Req *r)
free(s);
break;
case Qstats:
- if((s = mallocz(Rulebuf, 1)) == nil){
+ if((s = hitstext()) == nil){
+ respond(r, "out of memory");
+ return;
+ }
+ if((t = smprint("passed %d\ndropped %d\n\n%s", nallow, ndeny, s)) == nil){
+ free(s);
respond(r, "out of memory");
return;
}
- n = snprint(s, Rulebuf-1, "passed %d\ndropped %d\n\n", nallow, ndeny);
- fmthits(s+n, Rulebuf-1-n);
- readstr(r, s);
free(s);
+ readstr(r, t);
+ free(t);
break;
default:
respond(r, "not a readable file");
@@ -1259,7 +1264,7 @@ threadmain(int argc, char **argv)
fmtinstall('I', eipfmt);
fmtinstall('V', eipfmt);
fmtinstall('E', eipfmt);
- fmtinstall('M', eipfmt); /* fmtrules prints masks with it */
+ fmtinstall('M', eipfmt); /* rulestext prints masks with it */
rulepath = argv[0];
readrules(rulepath);