Note: This bug is displayed in read-only format because the product is no longer active in Red Hat Bugzilla.
RHEL Engineering is moving the tracking of its product development work on RHEL 6 through RHEL 9 to Red Hat Jira (issues.redhat.com). If you're a Red Hat customer, please continue to file support cases via the Red Hat customer portal. If you're not, please head to the "RHEL project" in Red Hat Jira and file new tickets here. Individual Bugzilla bugs in the statuses "NEW", "ASSIGNED", and "POST" are being migrated throughout September 2023. Bugs of Red Hat partners with an assigned Engineering Partner Manager (EPM) are migrated in late September as per pre-agreed dates. Bugs against components "kernel", "kernel-rt", and "kpatch" are only migrated if still in "NEW" or "ASSIGNED". If you cannot log in to RH Jira, please consult article #7032570. That failing, please send an e-mail to the RH Jira admins at rh-issues@redhat.com to troubleshoot your issue as a user management inquiry. The email creates a ServiceNow ticket with Red Hat. Individual Bugzilla bugs that are migrated will be moved to status "CLOSED", resolution "MIGRATED", and set with "MigratedToJIRA" in "Keywords". The link to the successor Jira issue will be found under "Links", have a little "two-footprint" icon next to it, and direct you to the "RHEL project" in Red Hat Jira (issue links are of type "https://issues.redhat.com/browse/RHEL-XXXX", where "X" is a digit). This same link will be available in a blue banner at the top of the page informing you that that bug has been migrated.

Bug 1949552

Summary: Firewalld direct rule ordering issue.
Product: Red Hat Enterprise Linux 8 Reporter: Tomas Dolezal <todoleza>
Component: firewalldAssignee: Eric Garver <egarver>
Status: CLOSED ERRATA QA Contact: Štěpán Němec <snemec>
Severity: medium Docs Contact:
Priority: unspecified    
Version: ---CC: egarver, jherron, qe-baseos-daemons, snemec, todoleza
Target Milestone: betaKeywords: Triaged, Upstream
Target Release: ---Flags: pm-rhel: mirror+
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: firewalld-0.9.3-5.el8 Doc Type: No Doc Update
Doc Text:
Story Points: ---
Clone Of: 1940928 Environment:
Last Closed: 2021-11-09 18:55:58 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 1940928    
Bug Blocks:    

Description Tomas Dolezal 2021-04-14 14:20:31 UTC
firewalld-0.9.3-1.el8.noarch affected as well

+++ This bug was initially created as a clone of Bug #1940928 +++

Description of problem:

Not sure if this has been reported yet but I found an ordering issue to affect all chains in any table when using firewalld direct interface. An ordering issue occurs in firewalld direct rules when using the argument -d subnet/mask,[.../..] (multiple network addresses or multiple single host addresses). I have tracked this down to /usr/lib/python2.7/site-packages/firewall/core/fw_direct.py 

To demonstrate this, going to just stick the private networks defined in RFC1918 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 in the example below. 

[1] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -m state --state ESTABLISHED,RELATED -j ACCEPT
[2] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT
[3] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT
[4] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 9 -j DROP

The above results in the below rulesets being injected into iptables in like below, we we can see the DROP rules is in the wrong place ( ie position 4 when it should be position 8)

]# iptables -t filter -nvL OUTPUT_direct
Chain OUTPUT_direct (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            10.0.0.0/8          
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.0.0/8          

But if you change the input order to something like below in [5-8]
 
firewall-cmd --direct --remove-rules ipv4 filter OUTPUT

[5] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -m state --state ESTABLISHED,RELATED -j ACCEPT
[6] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 9 -j DROP
[7] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT
[8] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT

You get the expected ordering 

iptables -t filter -nvL OUTPUT_direct
Chain OUTPUT_direct (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            10.0.0.0/8          
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.0.0/8         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            

This confirms a segmentation in how the rules are interpreted in firewalld --direct vs iptables. Meaning firewalld sees [1-4] or [5-8] as 4 direct rules but iptables sees [1-4] or [5-8] as eight individual rules. But this doesn't just affect the output chain in the filter table. This affects all tables and chains with iptables as a result of how firewalld passes the direct rule sets into iptables. I was also able to confirm this same behavior in RHEL 8 using iptables as the default backend. 

A more verbose output with debug logs. 

[1] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -m state --state ESTABLISHED,RELATED -j ACCEPT

2021-03-19 10:41:50 DEBUG2: <class 'firewall.core.ipXtables.ip4tables'>: /usr/sbin/iptables-restore /run/firewalld/temp.2TyrMn: 81
       1: *filter
       2: -I OUTPUT_direct 1 -m state --state ESTABLISHED,RELATED -j ACCEPT
       3: COMMIT
2021-03-19 10:41:50 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:41:50 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:41:50 DEBUG1: direct.RuleAdded('ipv4', 'filter', 'OUTPUT', 0, '-m','state','--state','ESTABLISHED,RELATED','-j','ACCEPT')

[2] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT

2021-03-19 10:42:22 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.pre()
2021-03-19 10:42:22 DEBUG2: <class 'firewall.core.ipXtables.ip4tables'>: /usr/sbin/iptables-restore /run/firewalld/temp.gumOoO: 172
       1: *filter
       2: -I OUTPUT_direct 2 -p tcp -d 10.0.0.0/8 -j ACCEPT
       3: -I OUTPUT_direct 2 -p tcp -d 172.16.0.0/16 -j ACCEPT
       4: -I OUTPUT_direct 2 -p tcp -d 192.168.0.0/24 -j ACCEPT
       5: COMMIT
2021-03-19 10:42:22 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:42:22 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:42:22 DEBUG1: direct.RuleAdded('ipv4', 'filter', 'OUTPUT', 2, '-p','tcp','-d','10.0.0.0/8,172.16.0.0/16,192.168.0.0/24','-j','ACCEPT')

[3] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT

2021-03-19 10:43:23 DEBUG2: <class 'firewall.core.ipXtables.ip4tables'>: /usr/sbin/iptables-restore /run/firewalld/temp.C4Gf08: 172
       1: *filter
       2: -I OUTPUT_direct 3 -p udp -d 10.0.0.0/8 -j ACCEPT
       3: -I OUTPUT_direct 3 -p udp -d 172.16.0.0/16 -j ACCEPT
       4: -I OUTPUT_direct 3 -p udp -d 192.168.0.0/24 -j ACCEPT
       5: COMMIT
2021-03-19 10:43:23 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:43:23 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:43:23 DEBUG1: direct.RuleAdded('ipv4', 'filter', 'OUTPUT', 2, '-p','udp','-d','10.0.0.0/8,172.16.0.0/16,192.168.0.0/24','-j','ACCEPT')

[4] firewall-cmd --direct --add-rule ipv4 filter OUTPUT 9 -j DROP

2021-03-19 10:43:49 DEBUG2: <class 'firewall.core.ipXtables.ip4tables'>: /usr/sbin/iptables-restore /run/firewalld/temp.4Vfavc: 42
       1: *filter
       2: -I OUTPUT_direct 4 -j DROP
       3: COMMIT
2021-03-19 10:43:49 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:43:49 DEBUG4: <class 'firewall.core.fw_transaction.FirewallTransaction'>.post()
2021-03-19 10:43:49 DEBUG1: direct.RuleAdded('ipv4', 'filter', 'OUTPUT', 9, '-j','DROP')

iptables -t filter -nvL OUTPUT_direct
Chain OUTPUT_direct (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            10.0.0.0/8          
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.0.0/8



--- Additional comment from Eric Garver on 2021-03-19 16:24:51 CET ---

This issue was introduced in commit 3451763190a7 ("firewall.core.ipXtables: Split up source and dest addresses for transaction"). That commit was to work around a limitation in iptables-restore which doesn't support -s/-d with multiple addresses.

A simple work around is to split direct rules with multiple addresses in -s and -d into multiple direct rules.
e.g.
instead of

  # firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8,172.16.0.0/16,192.168.0.0/24 -j ACCEPT

use three rules

  # firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8 -j ACCEPT
  # firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 172.16.0.0/16 -j ACCEPT
  # firewall-cmd --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 192.168.0.0/24 -j ACCEPT

--- Additional comment from Justin on 2021-03-19 17:00:59 CET ---

Ahh Okay, 

Good to know I was in the right area so to speak, but I proposed two workarounds as well to the case I have open, but the documentation ( ie man-pages ) as well as the a few kcs articles we have that point to the man pages ( ie man 5 firewall.direct and man 8 iptables ) which imply this is a valid configuration (ie Using a list of subnets or host addresses with the -d argument ). 

I could be somewhat off base, but If we don't plan to fix this due to the adoption of nftables, are we able to make an amendment in man 5 firewalld.direct advising when using iptables as a backend? I was planning on making a KCS article addressing this if not already made.

------------------------------------------
Documentation
------------------------------------------
man 5 firewalld.direct 

       Direct configuration gives a more direct access to the firewall. It requires user to know basic ip(6)tables/ebtables concepts, i.e.  table
       (filter/mangle/nat/...), chain (INPUT/OUTPUT/FORWARD/...), commands (-A/-D/-I/...), parameters (-p/-s/-d/-j/...) and targets (ACCEPT/DROP/REJECT/...). Direct
       configuration should be used only as a last resort when it's not possible to use firewalld.zone(5). See also Direct Options in firewall-cmd(1).

man 8 iptables 

       [!] -d, --destination address[/mask][,...]
              Destination specification.  See the description of the -s (source) flag for a detailed description of the syntax.  The flag --dst is an alias for  this
              option.

------------------------------------------
WORKAROUNDS
------------------------------------------
[1] Don't use a list of addresses or networks with the -d argument, which will give you the correct ordering. 

]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 0 -m state --state ESTABLISHED,RELATED -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 10.0.0.0/8 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 172.16.0.0/16 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p udp -d 192.168.0.0/24 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 10.0.0.0/8 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 172.16.0.0/16 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 2 -p tcp -d 192.168.0.0/24 -j ACCEPT
]# firewall-cmd --perm --direct --add-rule ipv4 filter OUTPUT 9 -j DROP

]# iptables -t filter -nvL OUTPUT_direct
Chain OUTPUT_direct (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            10.0.0.0/8          
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.0.0/8          
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            172.16.0.0/16       
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            192.168.0.0/24      
    8   608 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           
 
[2] Use ipset lists

Setting and Controlling IP sets using firewalld
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-setting_and_controlling_ip_sets_using_firewalld

firewall-cmd --permanent --new-ipset=allowed_out --type=hash:net
printf "172.16.0.0/16\n192.168.0.0/24\n10.0.0.0/8\n" > allowed.txt
firewall-cmd --permanent --ipset=allowed_out --add-entries-from-file=allowed.txt

/* Then create the rules. 
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -m state --state RELATED,ESTABLISHED -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 1 -m set --match-set allowed_out src -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 9 -j DROP
firewall-cmd --reload

iptables -t filter -nvL OUTPUT_direct
Chain OUTPUT_direct (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set allowed_out src
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

firewalld]# ipset -L
Name: allowed_out
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 568
References: 1
Number of entries: 3
Members:
172.16.0.0/16
192.168.0.0/24
10.0.0.0/8

--- Additional comment from Eric Garver on 2021-03-19 18:24:00 CET ---

(In reply to Justin from comment #4)
> Ahh Okay, 
> 
> Good to know I was in the right area so to speak, but I proposed two
> workarounds as well to the case I have open, but the documentation ( ie
> man-pages ) as well as the a few kcs articles we have that point to the man
> pages ( ie man 5 firewall.direct and man 8 iptables ) which imply this is a
> valid configuration (ie Using a list of subnets or host addresses with the
> -d argument ). 
> 
> I could be somewhat off base, but If we don't plan to fix this due to the
> adoption of nftables, are we able to make an amendment in man 5
> firewalld.direct advising when using iptables as a backend? I was planning
> on making a KCS article addressing this if not already made.

I will investigate fixing this. direct rules are still applicable when using the nftables backend. Although given the easy workaround I'm not sure the fix would be eligible for RHEL-7.

--- Additional comment from Justin on 2021-03-19 18:39:37 CET ---

Ack

Comment 11 errata-xmlrpc 2021-11-09 18:55:58 UTC
Since the problem described in this bug report should be
resolved in a recent advisory, it has been closed with a
resolution of ERRATA.

For information on the advisory (firewalld bug fix and enhancement update), and where to find the updated
files, follow the link below.

If the solution does not work for you, open a new bug report.

https://access.redhat.com/errata/RHBA-2021:4355