Hello,

patch below fixes various glitches I've found in pfctl. All issues are related
to anchors. I did test the patch using regress/sbin/pfctl. Although all tests
have passed, I still would like to ask for testing in field, especially if you
use some fancy configuration with 'load anchor'. The patch is just intended for
testing. I'll break the patch to smaller chunks to get some OKs.

the list of bugfixes is as follows:

    - pfctl_optimize.c is not able to create radix table. This bug was reported
      by Leonardo Guardati. It's yet another occurrence of 'name vs. path' mix
      up [1]

    - use after free, when pfctl_optimize fails to create radix table.  We are
      just (un)lucky enough the Leonardo hit [1] in pfctl_optimize.c

    - the use after free is triggered in error path of
      pfctl_optimize_ruleset(). I gave a closer look to function and spot a
      memory leak. Fortunately the memory leak is not critical, because the
      pfctl is just one-shot application: load rules and quit anyway.

    - when I implemented a test case using the rules from Leonardo I found
      yet another oddity in processing of 'load anchor'. The 'load anchor"
      is handled differently when the rules are being loaded to root
      anchor (pfctl -f pf-load-anchor.conf) and when the same rules are being
      loaded to non-root anchor (pfctl -a regression -f pf-load-anchor.conf).

The test case I've got from Leonardo is simple configuration using nested
anchors. There are just two nesting levels. The pf.conf file, which loads
level one looks as follows:
--------8<---------------8<---------------8<------------------8<--------
anchor "one"
load anchor "one" from "/home/sashan/leonardo/pf-optimize.one"
--------8<---------------8<---------------8<------------------8<--------

the pf-optimize.one file contains just simple anchor, which loads anchor
two:
--------8<---------------8<---------------8<------------------8<--------
anchor "two"
load anchor "two" from "/home/sashan/leonardo/pf-optimize.two"
--------8<---------------8<---------------8<------------------8<--------

finally the pf-optimize.two contains a ruleset, which triggers the
bugs found in pfctl_optimize.c:
--------8<---------------8<---------------8<------------------8<--------
addrs = "{      1.2.3.4,
                10.20.30.40,
                2.4.6.8,
                20.40.60.80,
                4.8.12.16,
                40.80.120.160,
                5.6.7.8,
                50.60.70.80,
                10.12.14.16,
                100.120.140.160
        }"
pass from $addrs 
--------8<---------------8<---------------8<------------------8<--------
As you can see the optimizer is supposed to  fold the list of addresses 'addrs'
to radix table. This currently fails because of 'name vs. path' mix up
in call to pfctl_define_table().

Assuming the 'mix up' is either fixed (or pf-optimize.two is altered to simple
'pass all') we can load the pf.conf to driver using 'pfctl -f pf.conf' command:
--------8<---------------8<---------------8<------------------8<--------
pfctl -f pf.conf
pfctl -vsA
  one
  one/two
--------8<---------------8<---------------8<------------------8<--------
The output of 'pfctl -vsA' is something we expect. Now let's try to load
the pf.conf using 'pfctl -a regress -f pf.conf' we get something like this:
--------8<---------------8<---------------8<------------------8<--------
pfctl -a regress -f pf.conf
pfctl -vsA                                                                      
                                                                                
                          
  regress
  regress/one
--------8<---------------8<---------------8<------------------8<--------
we are missing 'regress/one/two' anchor in output above. With patch applied we
get expected results:
--------8<---------------8<---------------8<------------------8<--------
pfctl -a regress -f /home/sashan/leonardo/pf-optimize.conf                      
                                                                                
                          
pfctl -vsA
  regress
  regress/one
  regress/one/two
--------8<---------------8<---------------8<------------------8<--------

I'll send individual fixes for review later on Friday.

Thank you for testing of patch below.

regards
sasha


[1] https://marc.info/?l=openbsd-tech&m=147292142630058&w=2

--------8<---------------8<---------------8<------------------8<--------
diff --git a/regress/sbin/pfctl/Makefile b/regress/sbin/pfctl/Makefile
index d42bb446e68..5572785b121 100644
--- a/regress/sbin/pfctl/Makefile
+++ b/regress/sbin/pfctl/Makefile
@@ -30,6 +30,7 @@ PFIF2IP=1 2 3
 PFCHKSUM=1 2 3
 PFCMD=1
 PFCMDFAIL=1
+PFLOADANCHORS=112 113
 
 MAKEOBJDIRPREFIX=
 
@@ -328,6 +329,20 @@ pfchksum-update:   ${PFCHKSUM_UPDATES}
 NODEFAULT_TARGETS+=pfchksum
 REGRESS_ROOT_TARGETS+=pfchksum
 
+.for n in ${PFLOADANCHORS}
+PFLOADANCHORS_TARGETS+=pfloadanchors${n}
+
+pfloadanchors${n}:
+       ${SUDO} pfctl -Fa > /dev/null 2>&1
+       ${SUDO} pfctl -a regress -f - < ${.CURDIR}/pf${n}.in
+       ${SUDO} pfctl -Fa > /dev/null 2>&1
+
+.endfor
+
+pfloadanchors:         ${PFLOADANCHORS_TARGETS}
+
+REGRESS_TARGETS+=pfloadanchors
+
 update:        ${UPDATE_TARGETS}
 
 alltests: ${REGRESS_TARGETS} ${NODEFAULT_TARGETS}
diff --git a/regress/sbin/pfctl/pf112.in b/regress/sbin/pfctl/pf112.in
new file mode 100644
index 00000000000..5b40dc0e69d
--- /dev/null
+++ b/regress/sbin/pfctl/pf112.in
@@ -0,0 +1,2 @@
+anchor "one"
+load anchor "one" from "pf112.one"
diff --git a/regress/sbin/pfctl/pf112.one b/regress/sbin/pfctl/pf112.one
new file mode 100644
index 00000000000..68e20033087
--- /dev/null
+++ b/regress/sbin/pfctl/pf112.one
@@ -0,0 +1,2 @@
+anchor "two"
+load anchor "two" from "pf112.two"
diff --git a/regress/sbin/pfctl/pf112.two b/regress/sbin/pfctl/pf112.two
new file mode 100644
index 00000000000..84e5f759569
--- /dev/null
+++ b/regress/sbin/pfctl/pf112.two
@@ -0,0 +1,2 @@
+table <foo> { 10.0.0.1 }
+pass from <foo>
diff --git a/regress/sbin/pfctl/pf113.in b/regress/sbin/pfctl/pf113.in
new file mode 100644
index 00000000000..f62fa7ab84a
--- /dev/null
+++ b/regress/sbin/pfctl/pf113.in
@@ -0,0 +1,2 @@
+anchor "one"
+load anchor "one" from "pf113.one"
diff --git a/regress/sbin/pfctl/pf113.one b/regress/sbin/pfctl/pf113.one
new file mode 100644
index 00000000000..4e4b63316b4
--- /dev/null
+++ b/regress/sbin/pfctl/pf113.one
@@ -0,0 +1,2 @@
+anchor "two"
+load anchor "two" from "pf113.two"
diff --git a/regress/sbin/pfctl/pf113.two b/regress/sbin/pfctl/pf113.two
new file mode 100644
index 00000000000..a99c64f94dc
--- /dev/null
+++ b/regress/sbin/pfctl/pf113.two
@@ -0,0 +1,12 @@
+addrs = "{     1.2.3.4,
+               10.20.30.40,
+               2.4.6.8,
+               20.40.60.80,
+               4.8.12.16,
+               40.80.120.160,
+               5.6.7.8,
+               50.60.70.80,
+               10.12.14.16,
+               100.120.140.160
+       }"
+pass from $addrs 
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index e1dcfbc382f..1016c909ccc 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -939,7 +939,7 @@ anchorrule  : ANCHOR anchorname dir quick interface af 
proto fromto
 loadrule       : LOAD ANCHOR string FROM string        {
                        struct loadanchors      *loadanchor;
 
-                       if (strlen(pf->anchor->name) + 1 +
+                       if (strlen(pf->anchor->path) + 1 +
                            strlen($3) >= PATH_MAX) {
                                yyerror("anchorname %s too long, max %u\n",
                                    $3, PATH_MAX - 1);
@@ -954,7 +954,7 @@ loadrule    : LOAD ANCHOR string FROM string        {
                                err(1, "loadrule: malloc");
                        if (pf->anchor->name[0])
                                snprintf(loadanchor->anchorname, PATH_MAX,
-                                   "%s/%s", pf->anchor->name, $3);
+                                   "%s/%s", pf->anchor->path, $3);
                        else
                                strlcpy(loadanchor->anchorname, $3, PATH_MAX);
                        if ((loadanchor->filename = strdup($5)) == NULL)
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index d6cfc132c0e..10d34c3ffa2 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1614,7 +1614,6 @@ pfctl_rules(int dev, char *filename, int opts, int 
optimize,
            sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name))
                errx(1, "pfctl_add_rule: strlcpy");
 
-
        pf.astack[0] = pf.anchor;
        pf.asd = 0;
        pf.trans = t;
@@ -1658,20 +1657,21 @@ pfctl_rules(int dev, char *filename, int opts, int 
optimize,
        free(path);
        path = NULL;
 
-       /* process "load anchor" directives that might have used queues */
-       if (!anchorname[0]) {
+       if (trans == NULL) {
+               /*
+                * process "load anchor" directives that might have used queues
+                */
                if (pfctl_load_anchors(dev, &pf, t) == -1)
                        ERRX("load anchors");
                pfctl_clear_queues(&qspecs);
                pfctl_clear_queues(&rootqs);
-       }
 
-       if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) {
-               if (!anchorname[0])
-                       if (pfctl_load_options(&pf))
+               if ((opts & PF_OPT_NOACTION) == 0) {
+                       if (!anchorname[0] && pfctl_load_options(&pf))
                                goto _error;
-               if (pfctl_trans(dev, t, DIOCXCOMMIT, osize))
-                       ERR("DIOCXCOMMIT");
+                       if (pfctl_trans(dev, t, DIOCXCOMMIT, osize))
+                               ERR("DIOCXCOMMIT");
+               }
        }
        return (0);
 
diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c
index a1b19781756..6ec429f2e06 100644
--- a/sbin/pfctl/pfctl_optimize.c
+++ b/sbin/pfctl/pfctl_optimize.c
@@ -238,6 +238,8 @@ int skip_cmp_src_addr(struct pf_rule *, struct pf_rule *);
 int    skip_cmp_src_port(struct pf_rule *, struct pf_rule *);
 int    superblock_inclusive(struct superblock *, struct pf_opt_rule *);
 void   superblock_free(struct pfctl *, struct superblock *);
+struct pf_opt_tbl *pf_opt_table_ref(struct pf_opt_tbl *);
+void   pf_opt_table_unref(struct pf_opt_tbl *);
 
 
 int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *, struct pf_rule *);
@@ -315,9 +317,11 @@ pfctl_optimize_ruleset(struct pfctl *pf, struct pf_ruleset 
*rs)
                                err(1, "calloc");
                        memcpy(r, &por->por_rule, sizeof(*r));
                        TAILQ_INSERT_TAIL(rs->rules.active.ptr, r, entries);
+                       pf_opt_table_unref(por->por_src_tbl);
+                       pf_opt_table_unref(por->por_dst_tbl);
                        free(por);
                }
-               free(block);
+               superblock_free(pf, block);
        }
 
        return (0);
@@ -325,16 +329,10 @@ pfctl_optimize_ruleset(struct pfctl *pf, struct 
pf_ruleset *rs)
 error:
        while ((por = TAILQ_FIRST(&opt_queue))) {
                TAILQ_REMOVE(&opt_queue, por, por_entry);
-               if (por->por_src_tbl) {
-                       pfr_buf_clear(por->por_src_tbl->pt_buf);
-                       free(por->por_src_tbl->pt_buf);
-                       free(por->por_src_tbl);
-               }
-               if (por->por_dst_tbl) {
-                       pfr_buf_clear(por->por_dst_tbl->pt_buf);
-                       free(por->por_dst_tbl->pt_buf);
-                       free(por->por_dst_tbl);
-               }
+               if (por->por_src_tbl)
+                       pf_opt_table_unref(por->por_src_tbl);
+               if (por->por_dst_tbl)
+                       pf_opt_table_unref(por->por_dst_tbl);
                free(por);
        }
        while ((block = TAILQ_FIRST(&superblocks))) {
@@ -502,13 +500,14 @@ combine_rules(struct pfctl *pf, struct superblock *block)
                                if (add_opt_table(pf, &p1->por_dst_tbl,
                                    p1->por_rule.af, &p2->por_rule.dst, NULL))
                                        return (1);
-                               p2->por_dst_tbl = p1->por_dst_tbl;
                                if (p1->por_dst_tbl->pt_rulecount >=
                                    TABLE_THRESHOLD) {
                                        TAILQ_REMOVE(&block->sb_rules, p2,
                                            por_entry);
                                        free(p2);
-                               }
+                               } else
+                                       p2->por_dst_tbl =
+                                           pf_opt_table_ref(p1->por_dst_tbl);
                        } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL
                            && p2->por_src_tbl == NULL &&
                            p2->por_dst_tbl == NULL &&
@@ -524,13 +523,14 @@ combine_rules(struct pfctl *pf, struct superblock *block)
                                if (add_opt_table(pf, &p1->por_src_tbl,
                                    p1->por_rule.af, &p2->por_rule.src, NULL))
                                        return (1);
-                               p2->por_src_tbl = p1->por_src_tbl;
                                if (p1->por_src_tbl->pt_rulecount >=
                                    TABLE_THRESHOLD) {
                                        TAILQ_REMOVE(&block->sb_rules, p2,
                                            por_entry);
                                        free(p2);
-                               }
+                               } else
+                                       p2->por_src_tbl =
+                                           pf_opt_table_ref(p1->por_src_tbl);
                        }
                }
        }
@@ -1218,6 +1218,7 @@ add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, 
sa_family_t af,
                    ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) ==
                    NULL)
                        err(1, "calloc");
+               (*tbl)->pt_refcnt = 1;
                (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS;
                SIMPLEQ_INIT(&(*tbl)->pt_nodes);
 
@@ -1307,7 +1308,7 @@ again:
        tablenum++;
 
        if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST | tbl->pt_flags, 1,
-           pf->astack[0]->name, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) {
+           pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) {
                warn("failed to create table %s in %s",
                    tbl->pt_name, pf->astack[0]->name);
                return (1);
@@ -1617,20 +1618,12 @@ superblock_free(struct pfctl *pf, struct superblock 
*block)
        struct pf_opt_rule *por;
        while ((por = TAILQ_FIRST(&block->sb_rules))) {
                TAILQ_REMOVE(&block->sb_rules, por, por_entry);
-               if (por->por_src_tbl) {
-                       if (por->por_src_tbl->pt_buf) {
-                               pfr_buf_clear(por->por_src_tbl->pt_buf);
-                               free(por->por_src_tbl->pt_buf);
-                       }
-                       free(por->por_src_tbl);
-               }
-               if (por->por_dst_tbl) {
-                       if (por->por_dst_tbl->pt_buf) {
-                               pfr_buf_clear(por->por_dst_tbl->pt_buf);
-                               free(por->por_dst_tbl->pt_buf);
-                       }
-                       free(por->por_dst_tbl);
-               }
+               if (por->por_src_tbl)
+                       pf_opt_table_unref(por->por_src_tbl);
+
+               if (por->por_dst_tbl)
+                       pf_opt_table_unref(por->por_dst_tbl);
+
                free(por);
        }
        if (block->sb_profiled_block)
@@ -1638,3 +1631,24 @@ superblock_free(struct pfctl *pf, struct superblock 
*block)
        free(block);
 }
 
+struct pf_opt_tbl *
+pf_opt_table_ref(struct pf_opt_tbl *pt)
+{
+       /* parser does not run concurrently, we don't need atomic ops. */
+       if (pt != NULL)
+               pt->pt_refcnt++;
+
+       return (pt);
+}
+
+void
+pf_opt_table_unref(struct pf_opt_tbl *pt)
+{
+       if ((pt != NULL) && ((--pt->pt_refcnt) == 0)) {
+               if (pt->pt_buf != NULL) {
+                       pfr_buf_clear(pt->pt_buf);
+                       free(pt->pt_buf);
+               }
+               free(pt);
+       }
+}
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 5963b6bdffd..de544da4172 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -62,15 +62,17 @@
 #include "pfctl_parser.h"
 #include "pfctl.h"
 
-void            print_op (u_int8_t, const char *, const char *);
-void            print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int);
-void            print_ugid (u_int8_t, unsigned, unsigned, const char *, 
unsigned);
-void            print_flags (u_int8_t);
-void            print_fromto(struct pf_rule_addr *, pf_osfp_t,
-                   struct pf_rule_addr *, u_int8_t, u_int8_t, int);
-void            print_bwspec(const char *index, struct pf_queue_bwspec *);
-void            print_scspec(const char *, struct pf_queue_scspec *);
-int             ifa_skip_if(const char *filter, struct node_host *p);
+void   print_op (u_int8_t, const char *, const char *);
+void   print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int);
+void   print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned);
+void   print_flags (u_int8_t);
+void   print_fromto(struct pf_rule_addr *, pf_osfp_t,
+           struct pf_rule_addr *, u_int8_t, u_int8_t, int);
+void   print_bwspec(const char *index, struct pf_queue_bwspec *);
+void   print_scspec(const char *, struct pf_queue_scspec *);
+int    ifa_skip_if(const char *filter, struct node_host *p);
+struct pfioc_trans_e *
+       pfctl_find_trans(struct pfr_buffer *, int, const char *);
 
 struct node_host       *ifa_grouplookup(const char *, int);
 struct node_host       *host_if(const char *, int);
@@ -1973,18 +1975,34 @@ append_addr_host(struct pfr_buffer *b, struct node_host 
*n, int test, int not)
        return (0);
 }
 
+struct pfioc_trans_e *
+pfctl_find_trans(struct pfr_buffer *buf, int type, const char *anchor)
+{
+       struct pfioc_trans_e *p = NULL;
+
+       PFRB_FOREACH(p, buf)
+               if (type == p->type && !strcmp(anchor, p->anchor))
+                       break;
+
+       return (p);
+}
+
 int
 pfctl_add_trans(struct pfr_buffer *buf, int type, const char *anchor)
 {
        struct pfioc_trans_e trans;
+       int rv = 0;
+
+       if (pfctl_find_trans(buf, type, anchor) == NULL) {
+               bzero(&trans, sizeof(trans));
+               trans.type = type;
+               if (strlcpy(trans.anchor, anchor,
+                   sizeof(trans.anchor)) >= sizeof(trans.anchor))
+                       errx(1, "pfctl_add_trans: strlcpy");
+               rv = pfr_buf_add(buf, &trans);
+       }
 
-       bzero(&trans, sizeof(trans));
-       trans.type = type;
-       if (strlcpy(trans.anchor, anchor,
-           sizeof(trans.anchor)) >= sizeof(trans.anchor))
-               errx(1, "pfctl_add_trans: strlcpy");
-
-       return pfr_buf_add(buf, &trans);
+       return (rv);
 }
 
 u_int32_t
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 7dacc34de1c..b83e2878c8a 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -175,6 +175,7 @@ struct pf_opt_tbl {
        int                      pt_rulecount;
        int                      pt_generated;
        u_int32_t                pt_flags;
+       u_int32_t                pt_refcnt;
        struct node_tinithead    pt_nodes;
        struct pfr_buffer       *pt_buf;
 };


Reply via email to