Hi tech@,

Syzkaller found a few crashes in pf_anchor_global_RB_REMOVE.
https://syzkaller.appspot.com/bug?id=a97f712331903ce38b8c084a489818b9bb5c6fcb
and also
https://syzkaller.appspot.com/text?tag=CrashLog&x=15ace9aaf00000

The call stack is something like this:
pf_anchor_global_RB_REMOVE
pf_remove_if_empty_ruleset
pfi_dynaddr_setup
pfioctl

I believe this is caused by two calls to pf_remove_if_empty_ruleset
in pfi_dynaddr_setup and a possibly pfr_destroy_ktable.

I think it is a problem that pfr_attach_table is being called
with 1 (intr) because the introducing commit says:

commit 4b3977248902c22d96aaebdb5784840debc2631c
Author: mikeb <mi...@openbsd.org>
Date:   Mon Nov 24 13:22:09 2008 +0000

    Fix splasserts seen in pr 5987 by propagating a flag that discribes
    whether we're called from the interrupt context to the functions
    performing allocations.

    Looked at by mpf@ and henning@, tested by mpf@ and Antti Harri,
    the pr originator.

    ok tedu

And we are not in interrupt context. A detailed analysis follows.


The pfioctl cd60441a is DIOCADDRULE.

The call stack fails in pf_addr_setup calls from pf_ioctl.c:
1647                         if (pf_addr_setup(ruleset, &newrule->src.addr,
1648                             newrule->af))
similar calls below.


One of these calls might do the following:
424 pfi_dynaddr_setup(struct pf_addr_wrap *aw, sa_family_t af)
...
462         if ((ruleset = pf_find_or_create_ruleset(PF_RESERVED_ANCHOR)) == 
NULL) {
assume false.
...
467         if ((dyn->pfid_kt = pfr_attach_table(ruleset, tblname, 1)) == NULL)
might evaluate to true, I will share my thoughts on it.
468                 rv = 1;
469                 goto _bad;
...
481 _bad:
482         if (dyn->pfid_kt != NULL)
483                 pfr_detach_table(dyn->pfid_kt);
484         if (ruleset != NULL)
485                 pf_remove_if_empty_ruleset(ruleset);
remember this call. I will show a case where we don't want this.

due to the following in pf_table.c:
2424 pfr_attach_table(struct pf_ruleset *rs, char *name, int intr)
...
2434         kt = pfr_lookup_table(&tbl);
2435         if (kt == NULL) {
assume true.
2436                 kt = pfr_create_ktable(&tbl, gettime(), 1, intr);
2437                 if (kt == NULL)
2438                         return (NULL);
2439                 if (ac != NULL) {
assume true.
this means kt != NULL and kt->pfrkt_rs = our ruleset.
2440                         bzero(tbl.pfrt_anchor, sizeof(tbl.pfrt_anchor));
2441                         rt = pfr_lookup_table(&tbl);
2442                         if (rt == NULL) {
assume true.
pfr_ktable_compares pfrkt_name an pfrkt_anchor if previous was same.
pfrkt_name should be '\0', pfrkt_anchor is PF_RESERVED_ANCHOR vs '\0'
2443                                 rt = pfr_create_ktable(&tbl, 0, 1, intr);
let's look into this call next just to be sure.
2444                                 if (rt == NULL) {
assume true.
2445                                         pfr_destroy_ktable(kt, 0);
remember this call is possible. We look at this afterwards.
2446                                         return (NULL);
...

looking into create_ktable, I want to see if we can reach pfr_destroy_ktable,
still in pf_table.c:
2202 pfr_create_ktable(struct pfr_table *tbl, time_t tzero, int attachruleset
...
2208         if (intr)
2209                 kt = pool_get(&pfr_ktable_pl, 
PR_NOWAIT|PR_ZERO|PR_LIMITFAIL);
This can fail. Returning NULL afterwards.
Note PR_ZERO.
...
2216         if (attachruleset) {
2217                 rs = pf_find_or_create_ruleset(tbl->pfrt_anchor);
2218                 if (!rs) {
not reachable because pfrt_anchor comes from l.2433.
But a weird indirection for our current call stack anyway. Might be necessary.
2219                         pfr_destroy_ktable(kt, 0);
2220                         return (NULL);
2221                 }
2222                 kt->pfrkt_rs = rs;
2223                 rs->tables++;
2224         }
2225
2226         if (!rn_inithead((void **)&kt->pfrkt_ip4,
2227             offsetof(struct sockaddr_in, sin_addr)) ||
2228             !rn_inithead((void **)&kt->pfrkt_ip6,
2229             offsetof(struct sockaddr_in6, sin6_addr))) {
unreachable because &kt->pfrkt_ip{4,6} are NULL.
2230                 pfr_destroy_ktable(kt, 0);
2231                 return (NULL);
2232         }

Back to pfr_destroy_ktable, still in pf_table.c:
2253 pfr_destroy_ktable(struct pfr_ktable *kt, int flushaddr)
2254 {
...
2268         if (kt->pfrkt_rs != NULL) {
assume true.
2269                 kt->pfrkt_rs->tables--;
2270                 pf_remove_if_empty_ruleset(kt->pfrkt_rs);
our first call to pf_remove_if_empty_ruleset.
The second one follows from pfi_dynaddr_setup.

If this is sound, then the only reason why pfr_destroy_ktable was called
is that pool_get is called with PR_NOWAIT.  And then the following diff
would help.

mbuhl


Index: pf_if.c
===================================================================
RCS file: /cvs/src/sys/net/pf_if.c,v
retrieving revision 1.104
diff -u -p -r1.104 pf_if.c
--- pf_if.c     29 Apr 2022 09:55:43 -0000      1.104
+++ pf_if.c     3 May 2022 16:01:23 -0000
@@ -464,7 +464,7 @@ pfi_dynaddr_setup(struct pf_addr_wrap *a
                goto _bad;
        }
 
-       if ((dyn->pfid_kt = pfr_attach_table(ruleset, tblname, 1)) == NULL) {
+       if ((dyn->pfid_kt = pfr_attach_table(ruleset, tblname, 0)) == NULL) {
                rv = 1;
                goto _bad;
        }

Reply via email to