Bug 260867 - pf: divert-to packets infinitely loop when written back to divert socket
Summary: pf: divert-to packets infinitely loop when written back to divert socket
Status: Open
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: CURRENT
Hardware: Any Any
: --- Affects Some People
Assignee: freebsd-pf (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-01-01 19:30 UTC by Damjan Jovanovic
Modified: 2023-10-20 13:58 UTC (History)
3 users (show)

See Also:


Attachments
Divert socket test code (1.64 KB, text/plain)
2022-01-01 19:30 UTC, Damjan Jovanovic
no flags Details
Test code to test pf divert-to (1.88 KB, text/plain)
2023-07-14 07:05 UTC, Alfa
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Damjan Jovanovic 2022-01-01 19:30:57 UTC
Created attachment 230608 [details]
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.
Comment 1 Graham Perrin freebsd_committer freebsd_triage 2022-10-17 12:36:15 UTC
Keyword: 

    patch
or  patch-ready

– in lieu of summary line prefix: 

    [patch]

* bulk change for the keyword
* summary lines may be edited manually (not in bulk). 

Keyword descriptions and search interface: 

    <https://bugs.freebsd.org/bugzilla/describekeywords.cgi>
Comment 2 Alfa 2023-07-14 07:05:16 UTC
Created attachment 243379 [details]
Test code to  test pf divert-to
Comment 3 Alfa 2023-07-14 11:34:41 UTC
(In reply to Alfa from comment #2)

Hi, i have the same infinity loop problem , i have tried PF Divert rules given below on between FreeBSD 11.0 to 14.0 CURRENT versions. There is same problem with all versions.It seems to me no work has been done to fix pf divert. By the way i am currently using both IPFW and PF at the same time, i use IPFW for DIVERT but i am trying to move on FreeBSD 14.0 to work with only PF . But this DIVERT is not working on FreeBSD 14.0-CURRENT pf. So i couldn't give up IPFW's DIVERT.
I have atteched a code above the attachment and i have tried all available codes on the internet.

LAN =igb1

pass in quick on igb1 proto udp from any to port { 53 } divert-to 127.0.0.1 port 3355

# I have found this rule (pass out quick on igb1 inet proto udp from any to port 53 flags S/SA keep state divert-reply) from google but i got this error:
/etc/pf.conf:83: divert-reply has no meaning in FreeBSD pf(4)
pfctl: Syntax error in config file: pf rules not loaded


FreeBSD 14.0-CURRENT pf.conf(5) man page

     divert-to <host> port <port>
	   Used	to redirect packets to a local socket bound to host and	port.
	   The packets will not	be modified, so	getsockname(2) on the socket
	   will	return the original destination	address	of the packet.

     divert-reply
	   Used	to receive replies for sockets that are	bound to addresses
	   which are not local to the machine.	See setsockopt(2) for informa-
	   tion	on how to bind these sockets.
Comment 4 Igor Ostapenko 2023-10-20 13:58:32 UTC
(In reply to Damjan Jovanovic from comment #0)

It's fixed in CURRENT, see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=272770. Please, give it a try and let us know if the issue persists.