[Bug 260867] [pf][patch] divert-to packets infinitely loop when written back to divert socket

From: <bugzilla-noreply_at_freebsd.org>
Date: Sat, 01 Jan 2022 19:30:57 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=260867

            Bug ID: 260867
           Summary: [pf][patch] divert-to packets infinitely loop when
                    written back to divert socket
           Product: Base System
           Version: CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Some People
          Priority: ---
         Component: kern
          Assignee: bugs@FreeBSD.org
          Reporter: damjan.jov@gmail.com
 Attachment #230608 text/plain
         mime type:

Created attachment 230608
  --> https://bugs.freebsd.org/bugzilla/attachment.cgi?id=230608&action=edit
Divert socket test code

On https://forums.freebsd.org/threads/pf-divert-to-loop-problem.81508 the
poster describes how the "divert-to" rule creates packet loops on FreeBSD 12.2,
and I also independently reproduced this bug on 13.0 and 14-CURRENT too.

It can be reproduced with this pf rule:

pass out on em0 divert-to 0.0.0.0 port 2000

while running the attached C code, which binds a divert socket to 0.0.0.0:2000
and reads packets and writes them back unchanged.

Adding some logging to the pf kernel module, I noticed that the
PF_PACKET_LOOPED flag never gets set in the pf_test() function. Checking for
conditions that set it which aren't being met, I think I found out why.

The following one line change fixes the issue for me:

---snip---
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 1686def4626..bd71d338517 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -6496,7 +6496,7 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct
mbuf **m0, struct inpcb *
        if (__predict_false(ip_divert_ptr != NULL) &&
            ((ipfwtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL)) != NULL)) {
                struct ipfw_rule_ref *rr = (struct ipfw_rule_ref *)(ipfwtag+1);
-               if (rr->info & IPFW_IS_DIVERT && rr->rulenum == 0) {
+               if (rr->info & IPFW_IS_DIVERT /*&& rr->rulenum == 0*/) {
                        if (pd.pf_mtag == NULL &&
                            ((pd.pf_mtag = pf_get_mtag(m)) == NULL)) {
                                action = PF_DROP;
---snip---


Why does that work?

It appears that the "rulenum" field is only written to in this one place:
                        ((struct ipfw_rule_ref *)(ipfwtag+1))->rulenum = dir;

and if "dir" is what I think it is, then as per /usr/include/netpfil/pf/pf.h:

enum    { PF_INOUT, PF_IN, PF_OUT };

the 0 in "rr->rulenum == 0" would be PF_INOUT, which packets never are. However
checking for values 1 and 2 instead, didn't seem to fix the issue either. Only
deleting the entire rr->rulenum check seems to fix it.

-- 
You are receiving this mail because:
You are the assignee for the bug.