Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 26 Jan 2024 14:32:39 -0500
From:      Karim Fodil-Lemelin <kfodil-lemelin@xiplink.com>
To:        Mikhail Holt <mikhail.k.holt@gmail.com>, freebsd-ipfw@FreeBSD.org, FreeBSD Net <freebsd-net@freebsd.org>
Cc:        Kristof Provost <kp@FreeBSD.org>, imp@freebsd.org
Subject:   Re: tag/untag
Message-ID:  <427708c9-7d62-443a-ad0f-4848bf8aff8f@xiplink.com>
In-Reply-To: <7915c1d1-99a3-4459-85ac-9451bec06c24@xiplink.com>
References:  <7915c1d1-99a3-4459-85ac-9451bec06c24@xiplink.com>

next in thread | previous in thread | raw e-mail | index | archive | help
--------------UN2Nz2O9jdpjqwIdAJgUl4uu
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

Hi Mikhail,

This is a very old bug in FreeBSD/ipfw O_PROBE_STATE and the good news 
is I have a fix for it. The fix is very easy and I will try to explain 
here what the bug is and what the fix is. The issue is only related to 
rules that have the F_NOT bit set and that are the beginning of the 
actions list of a keep-state rule. I'm also CCing some other folks that 
have authority in moving this forward so it hopefully gets into main 
eventually.

If you are like me, this is a perfect read for a Friday afternoon ;)

So here is how it goes:

When you create a keep-state rule, the userland part of ipfw will insert 
a O_PROBE_STATE action at the very beginning of the rule so when a 
packet hits the rule the kernel will PROBE the list of installed dynamic 
rules for a match before installing such a rule (we don't want to create 
more than one dynamic rule per parent rule). The code responsible for 
this looks like this in ipfw2.c:

         /*
          * generate O_PROBE_STATE if necessary
          */
         if (have_state && have_state->opcode != O_CHECK_STATE && 
!have_rstate) {
                 fill_cmd(dst, O_PROBE_STATE, 0, have_state->arg1);
                 dst = next_cmd(dst, &rblen);
         }

Now ipfw userland also does something else when building the list of 
actions for a rule, it records the actions offset in the rule and put 
those actions together in a predetermined way, here is the section of 
ipfw2.c that does something like this:

         /*
          * start action section
          */
         rule->act_ofs = dst - rule->cmd;

         /* put back O_LOG, O_ALTQ, O_TAG if necessary */
log --> if (have_log) {
                 i = F_LEN(have_log);
                 CHECK_RBUFLEN(i);
                 bcopy(have_log, dst, i * sizeof(uint32_t));
                 dst += i;
         }
         if (have_altq) {
                 i = F_LEN(have_altq);
                 CHECK_RBUFLEN(i);
                 bcopy(have_altq, dst, i * sizeof(uint32_t));
                 dst += i;
         }
tag --> if (have_tag) {
                 i = F_LEN(have_tag);
                 CHECK_RBUFLEN(i);
                 bcopy(have_tag, dst, i * sizeof(uint32_t));
                 dst += i;
         }

Nothing wrong with all this and if you are still with me (kudos to you) 
please take a moment to notice the 'log' action is _before_ the 'tag' 
action. This will be important later to understand why some rules in 
your example works and why some don't, although you may already have a 
clue...

Now let's transport ourselves into the kernel code, precisely in 
sys/netpfil/ipfw/ip_fw2.c around line 2865. Here we are inside the 
O_PROBE_STATE case and notice how after we find a dynamic entry how the 
kernel will 'reset' the cmd pointer to the action part of the parent 
rule by doing something like this:

/*
                                          * Found dynamic entry, jump to the
                                          * 'action' part of the parent rule
                                          * by setting f, cmd, l and 
clearing
                                          * cmdlen.
                                          */
                                         f = q;
                                         f_pos = dyn_info.f_pos;
cmd pointer is reset -->                cmd = ACTION_PTR(f);
                                         l = f->cmd_len - f->act_ofs;
                                         cmdlen = 0;
                                         match = 1;
                                         break;
                                 }

At this precise moment (pointed by the -->), if we refer to your example 
below, we would be hitting the dynamic rule entry and have cmd pointer 
reset to the ACTION part of the 'untag 10' action you have entered. We 
then set a few things and break from the switch statement. Now this gets 
us at the end of the switch statement where FreeBSD does something like 
this (around line 3337):

/*
                          * if we get here with l=0, then match is 
irrelevant.
                          */

cmd is 'untag' -->      if (cmd->len & F_NOT)
                                 match = !match;

                         if (match) {
                                 if (cmd->len & F_OR)
                                         skip_or = 1;
                         } else {
                                 if (!(cmd->len & F_OR)) /* not an OR 
block, */
exits too early -->                     break;          /* try next 
rule    */
                         }

                 }       /* end of inner loop, scan opcodes */

If you look at the check for F_NOT bit (which is cleverly folded in the 
len part of the cmd) you realize that check is actually made against the 
'untag' action and since untag is actually NOT tag it will match and 
change match into !match. The code will not try to go through the actual 
action and it will exit the loop too early.

Essentially, by resetting the cmd action pointer we should give a chance 
for O_TAG (F_NOT) to be called and not simply turn match into a no 
match. The fix for this is very simple and goes like this:

diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c
index d2b01fd..57c02dc 100644
--- a/sys/netpfil/ipfw/ip_fw2.c
+++ b/sys/netpfil/ipfw/ip_fw2.c
@@ -2887,7 +2887,8 @@ do { \
                                   l = f->cmd_len - f->act_ofs;
                                   cmdlen = 0;
                                   match = 1;
-                                 break;
+                                 continue;
+                                 break; /* not reached */
                                 }
                                 /*
                                  * Dynamic entry not found. If CHECK_STATE,


Now why did it work for you when you used log? Well I'm sure you 
remember that, because O_LOG is inserted _before_ O_TAG and that O_LOG 
doesn't have the F_NOT bit set then match is still 1 at the end of the 
loop and the actual cmd (LOG in this case) will have a chance to execute.

Finally, if your still reading, I think this bug has been in ipfw for a 
very long time, great catch buddy!

Best,

Karim.

PS: If you feel like compiling a kernel with my fix and testing it I'm 
sure the community would appreciate your feedback.

On 2023-11-08 2:26 a.m., Mikhail Holt wrote:
> Hello List,
>
> On a recent Stable 13 test host I, by accident, found that:
>
> /sbin/ipfw -q add 0031 allow              tcp from 192.168.64.0/24 to 
> me dst-port ssh in via igb3 setup keep-state   WORKS
>
> /sbin/ipfw -q add 0031 allow log          tcp from 192.168.64.0/24 to 
> me dst-port ssh in via igb3 setup keep-state   WORKS
>
> /sbin/ipfw -q add 0031 allow log tag   10 tcp from 192.168.64.0/24 to 
> me dst-port ssh in via igb3 setup keep-state   WORKS
>
> /sbin/ipfw -q add 0031 allow log untag 10 tcp from 192.168.64.0/24 to 
> me dst-port ssh in via igb3 setup keep-state   WORKS
>
> /sbin/ipfw -q add 0031 allow     untag 10 tcp from 192.168.64.0/24 to 
> me dst-port ssh in via igb3 setup keep-state   DOES NOT WORK?
> - A dynamic rule is created as per the rules that work.
> - Packets are logged by a deny all rule which of course is never 
> reached by the rules that work.
>
> Not a real issue for me but thought it worth noting.
>
> Mik.

--------------UN2Nz2O9jdpjqwIdAJgUl4uu
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    Hi Mikhail,
    <div class="moz-forward-container">
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">This is a very old bug in
        FreeBSD/ipfw O_PROBE_STATE and the good news is I have a fix for
        it. The fix is very easy and I will try to explain here what the
        bug is and what the fix is. The issue is only related to rules
        that have the F_NOT bit set and that are the beginning of the
        actions list of a keep-state rule. I'm also CCing some other
        folks that have authority in moving this forward so it hopefully
        gets into main eventually.</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">If you are like me, this is a perfect
        read for a Friday afternoon ;)</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">So here is how it goes:</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">When you create a keep-state rule,
        the userland part of ipfw will insert a O_PROBE_STATE action at
        the very beginning of the rule so when a packet hits the rule
        the kernel will PROBE the list of installed dynamic rules for a
        match before installing such a rule (we don't want to create
        more than one dynamic rule per parent rule). The code
        responsible for this looks like this in ipfw2.c:</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix"><font face="monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * generate O_PROBE_STATE if necessary<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (have_state &amp;&amp; have_state-&gt;opcode !=
          O_CHECK_STATE &amp;&amp; !have_rstate) {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fill_cmd(dst, O_PROBE_STATE, 0,
          have_state-&gt;arg1);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dst = next_cmd(dst, &amp;rblen);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;</font><br>
      </div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Now ipfw userland also does something
        else when building the list of actions for a rule, it records
        the actions offset in the rule and put those actions together in
        a predetermined way, here is the section of ipfw2.c that does
        something like this:</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix"><font face="monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * start action section<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rule-&gt;act_ofs = dst - rule-&gt;cmd;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* put back O_LOG, O_ALTQ, O_TAG if necessary */<br>
          log --&gt; if (have_log) {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = F_LEN(have_log);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHECK_RBUFLEN(i);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bcopy(have_log, dst, i * sizeof(uint32_t));<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dst += i;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (have_altq) {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = F_LEN(have_altq);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHECK_RBUFLEN(i);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bcopy(have_altq, dst, i * sizeof(uint32_t));<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dst += i;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
          tag --&gt; if (have_tag) {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = F_LEN(have_tag);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHECK_RBUFLEN(i); <br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bcopy(have_tag, dst, i * sizeof(uint32_t));<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dst += i;&nbsp; &nbsp;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
        </font><br>
      </div>
      <div class="moz-cite-prefix">Nothing wrong with all this and if
        you are still with me (kudos to you) please take a moment to
        notice the 'log' action is _before_ the 'tag' action. This will
        be important later to understand why some rules in your example
        works and why some don't, although you may already have a
        clue...</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Now let's transport ourselves into
        the kernel code, precisely in sys/netpfil/ipfw/ip_fw2.c around
        line 2865. Here we are inside the O_PROBE_STATE case and notice
        how after we find a dynamic entry how the kernel will 'reset'
        the cmd pointer to the action part of the parent rule by doing
        something like this:</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix"><font face="monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          /*<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * Found dynamic
          entry, jump to the<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 'action' part of
          the parent rule<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * by setting f, cmd,
          l and clearing<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * cmdlen.<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f = q;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f_pos =
          dyn_info.f_pos;<br>
          cmd pointer is reset --&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmd =
          ACTION_PTR(f);<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; l = f-&gt;cmd_len -
          f-&gt;act_ofs;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmdlen = 0;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match = 1;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
        </font><br>
      </div>
      <div class="moz-cite-prefix">At this precise moment (pointed by
        the --&gt;), if we refer to your example below, we would be
        hitting the dynamic rule entry and have cmd pointer reset to the
        ACTION part of the 'untag 10' action you have entered. We then
        set a few things and break from the switch statement. Now this
        gets us at the end of the switch statement where FreeBSD does
        something like this (around line 3337):</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix"><font face="monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          /*<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * if we get here with l=0, then match
          is irrelevant.<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>
          <br>
          cmd is 'untag' --&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (cmd-&gt;len &amp; F_NOT)<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match = !match;<br>
          <br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (match) {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (cmd-&gt;len &amp; F_OR)<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skip_or = 1;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!(cmd-&gt;len &amp; F_OR))
          /* not an OR block, */<br>
          exits too early --&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*
          try next rule&nbsp;&nbsp;&nbsp; */<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
          <br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* end of inner loop, scan opcodes */<br>
        </font><br>
      </div>
      <div class="moz-cite-prefix">If you look at the check for F_NOT
        bit (which is cleverly folded in the len part of the cmd) you
        realize that check is actually made against the 'untag' action
        and since untag is actually NOT tag it will match and change
        match into !match. The code will not try to go through the
        actual action and it will exit the loop too early.<br>
      </div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Essentially, by resetting the cmd
        action pointer we should give a chance for O_TAG (F_NOT) to be
        called and not simply turn match into a no match. The fix for
        this is very simple and goes like this:</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix"><font face="monospace">diff --git
          a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c<br>
          index d2b01fd..57c02dc 100644<br>
          --- a/sys/netpfil/ipfw/ip_fw2.c<br>
          +++ b/sys/netpfil/ipfw/ip_fw2.c<br>
          @@ -2887,7 +2887,8 @@ do
          {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          \<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; l = f-&gt;cmd_len -
          f-&gt;act_ofs;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmdlen = 0;<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match = 1;<br>
          -&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>
          +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>
          +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; /* not reached */<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>
          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * Dynamic entry not found. If
          CHECK_STATE,<br>
          <br>
        </font></div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Now why did it work for you when you
        used log? Well I'm sure you remember that, because O_LOG is
        inserted _before_ O_TAG and that O_LOG doesn't have the F_NOT
        bit set then match is still 1 at the end of the loop and the
        actual cmd (LOG in this case) will have a chance to execute.</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Finally, if your still reading, I
        think this bug has been in ipfw for a very long time, great
        catch buddy!<br>
      </div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Best,</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">Karim.</div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">PS: If you feel like compiling a
        kernel with my fix and testing it I'm sure the community would
        appreciate your feedback.<br>
      </div>
      <div class="moz-cite-prefix"><br>
      </div>
      <div class="moz-cite-prefix">On 2023-11-08 2:26 a.m., Mikhail Holt
        wrote:<br>
      </div>
      <blockquote type="cite" cite="mid:654B381A.10705@gmail.com"> <font face="Courier">Hello List,<br>
          <br>
          On a recent Stable 13 test host I, by accident, found that:<br>
          <br>
          /sbin/ipfw -q add 0031 allow&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tcp from
          192.168.64.0/24 to me dst-port ssh in via igb3 setup
          keep-state&nbsp;&nbsp; WORKS<br>
          <br>
          /sbin/ipfw -q add 0031 allow log&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tcp from
          192.168.64.0/24 to me dst-port ssh in via igb3 setup
          keep-state&nbsp;&nbsp; WORKS<br>
          <br>
          /sbin/ipfw -q add 0031 allow log tag&nbsp;&nbsp; 10 tcp from
          192.168.64.0/24 to me dst-port ssh in via igb3 setup
          keep-state&nbsp;&nbsp; WORKS<br>
          <br>
          /sbin/ipfw -q add 0031 allow log untag 10 tcp from
          192.168.64.0/24 to me dst-port ssh in via igb3 setup
          keep-state&nbsp;&nbsp; WORKS<br>
          <br>
          /sbin/ipfw -q add 0031 allow&nbsp;&nbsp;&nbsp;&nbsp; untag 10 tcp from
          192.168.64.0/24 to me dst-port ssh in via igb3 setup
          keep-state&nbsp;&nbsp; DOES NOT WORK?<br>
          - A dynamic rule is created as per the rules that work.<br>
          - Packets are logged by a deny all rule which of course is
          never reached by the rules that work.<br>
          <br>
          Not a real issue for me but thought it worth noting.<br>
          <br>
          Mik.</font><br>
      </blockquote>
      <br>
    </div>
  </body>
</html>

--------------UN2Nz2O9jdpjqwIdAJgUl4uu--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?427708c9-7d62-443a-ad0f-4848bf8aff8f>