FS#65220 - [nftables] Issues with nftables.conf and nftables.service. Breaks IPv6 and insecure reloading.

Attached to Project: Arch Linux
Opened by igo95862 (igo95862) - Monday, 20 January 2020, 15:59 GMT
Last edited by Sébastien Luttringer (seblu) - Tuesday, 13 April 2021, 09:56 GMT
Task Type Bug Report
Category Packages: Extra
Status Closed
Assigned To Sébastien Luttringer (seblu)
Architecture All
Severity Low
Priority Normal
Reported Version
Due in Version Undecided
Due Date Undecided
Percent Complete 100%
Votes 1
Private No

Details

Description:

While writting nftables wiki article I have identified multiple issues with the nftables.conf file shipped with nftables package. Since that file seems to be made by package maintainer I am reporting this here.

Theory:

To understand why am I reporting this lets go over what a simple firewall does and what it is supposed to protect.

If you read wiki article on Simple Stateful Firewall or the /etc/iptables/simple_firewall.rules you can see that the idea behind simple firewall is to allow all outgoing connections but block all incoming and forwarded packets. So essentially the simple firewall is invisible to user but protects the services run from accidental exposure to the outside.




Issues found:

* nftables.service flushes entire rule set when reloaded or stopped. This will break things like docker if it switches to nftables. I think it should only flush the table that it created.


* nftables.service does not reload atomically. If you made a typo in the nftables.conf and try to restart your system WILL BE LEFT WIDE OPEN. (sorry for the caps) Nftables fully supports atomic reloads, however, the issue is how nftables.service is defined. ExecStop flushes rule set essentially opening the system. This also means there is a firewall hole between when ExecRestart and ExecStop. (anytime you restart a service ExecStop will be invoked)

I think the best solution would be changing the type of service from "oneshot" to "simple". This will change restart behavior: ExecStop won't be invoked on restarts.


* DHCPv6 clients do not work. (i.e. NetworkManager, dhcpcd) Meaning if you connect to IPv6 network your resolve.conf DNS servers most likely won't update. If that network employs statefull address assignment when your computer won't be able to acquire address automatically. The issue is how DHCPv6 works. Firewalld had a similar issue but currently deploys a rule to make DHCPv6 function as a default configuration: https://unix.stackexchange.com/questions/176717/what-is-dhcpv6-client-service-in-firewalld-and-can-i-safely-remove-it


* ICMPv6 with extension headers will not work. The issue is "ip6 nexthdr ipv6-icmp accept" rule that determines the icmpv6 protocol only by first header. From nftables man page: "Caution when using ip6 nexthdr, the value only refers to the next header, i.e. ip6 nexthdr tcp will only match if the ipv6 packet does not contain any extension headers. Please use meta l4proto if you wish to match the real transport header and ignore any additional extension headers instead." You might ask if ICMPv6 can have extension headers and if you read RFC 4443 https://tools.ietf.org/html/rfc4443 you can find following: "Every ICMPv6 message is preceded by an IPv6 header and zero or more IPv6 extension headers."


* Style of variables. As was discussed on nftables wiki page, naming chains or tables same names as some keywords makes it very confusing what is a variable that you can change without any side effect and what is an actual keyword. For example, "filter" is name of the table but "filter" is actually a keyword in the chain type. I recommend giving it a verbose name. Since this is a simple firewall why not name variables with simple_ prefix.



PS. What is going on in /usr/share/nftables? All the examples are empty. Take the *-nat.nft examples. They are not NATing anything.

PPS. Should nftables.service be added as Conflict with firewalld. As far as I know firewalld is the only firewall that also uses nftables.


Additional info:
* package version: 1:0.9.3-1


My recommended configuration:

/etc/nftables.conf

# ipv4/ipv6 Simple & Safe Firewall
# you can find examples at
# https://wiki.archlinux.org/index.php/Nftables#Examples

# Atomic reload.
#
# Table is flushed before new rules are applied.
# This is done within a single file
# meaning if you made a typo and new rules
# are invalid your old rules won't be flushed
add table inet simple_firewall
flush table inet simple_firewall


table inet simple_firewall {
chain simple_input {
# This comment explains the syntax of chain defintition.
#
# Chain type: filter
# Filter is default type of chain.
# It supports all hooks and all table families
#
# Hook type: input
# Input hook processes all incomming packets
#
# Priority: 0
# This determines the order of evaluation of
# chain on the same hook. Lower values are
# evaluated first.
type filter hook input priority 0
# Default action is drop
policy drop
# Allow established/related connections
ct state {established, related} accept

# Early drop of invalid connections
ct state invalid drop

# Allow from loopback
iifname lo accept

# Allow icmp and icmpv6
meta l4proto { icmp, ipv6-icmp } accept

# Allow DHCPv6 incomming replies
ip6 daddr fe80::/64 udp sport 547 udp dport 546 ct state { new, untracked } accept

# Allow ssh
tcp dport ssh accept

# Everything else is rejected because of policy reject
}

chain simple_forward {
# This chain hooks to forwarded packets
# Meaning it processes the packets
# that we recieved but the destination
# is a different address.
#
# If we were a router we would send those
# packets to reciepient but we are
# end point.
type filter hook forward priority 0

policy drop
}

}

# vim:set ts=2 sw=2 et:

nfttables.service

[Unit]
Description=Nftables simple and secure firewall
Documentation=man:nft(8)
Wants=network-pre.target
Before=network-pre.target

[Service]
Type=simple
ExecStart=/usr/bin/nft --file /etc/nftables.conf
ExecStop=/usr/bin/nft flush table inet simple_firewall ';' delete table inet simple_firewall
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
This task depends upon

Closed by  Sébastien Luttringer (seblu)
Tuesday, 13 April 2021, 09:56 GMT
Reason for closing:  Fixed
Comment by igo95862 (igo95862) - Monday, 20 January 2020, 16:02 GMT
Seems like flyspray broke formatting. I am attaching the files that are formatted properly.
Comment by igo95862 (igo95862) - Monday, 20 January 2020, 21:05 GMT
I investigated a bit more. Here are more notes:

Tables can be deleted even when they have rules. (archwiki has wrong information that I corrected) This means ExecStop can be simplified.

ExecStop=/usr/bin/nft delete table inet simple_firewall


Second, the new rules I posted will drop packets while old one would have rejected. I am not sure what is the better approach. The "reject with icmpx type port-unreachable" can be simplified to "reject". Also the dropping of forwarded packets can be replaced with "reject with icmpx type no-route"

If you think that dropping packets would be better approach when "# Everything else is rejected because of policy reject" should probably be changed to "# Everything else is dropped because of policy drop"
Comment by Sébastien Luttringer (seblu) - Monday, 20 January 2020, 22:25 GMT
There is too much topics for one bug report. I'm paid by resolved bug report, more you open, more I'll get money.

* Regarding the full ruleset flush.
From what I tested, even if we don't flush the whole ruleset it will break docker. But, I have a use case with sshguard, where restarting nftables.service, break the sshguard table. That hurt me too.

But, is a start/restart of nftables service should set/reset the firewall in a predictable defined state or not. It needed to manage it on my servers with sshguard but I don't known if it's better to change it or not. I'm still in a Quantum decoherence state.

* ICMPv6 with extension headers will not work
Correct, I'll change that.

* DHCPv6 clients do not work.
UDP 67 is not open for bootp/dhcp v4. Why should we allow dhcp by default?
The default file is dropping everything but ssh.

* Style of variables
Don't find the reference in wiki. Looks like bike-shedding.

* What is going on in /usr/share/nftables?
Files come from upstream.

* Should nftables.service be added as Conflict with firewalld
sshguard also use nftables. You are proposing to make nftables.service using it's own table. Firewalld is using a table named firewalld. There is no need to make them conflict if we do that.
Comment by igo95862 (igo95862) - Tuesday, 21 January 2020, 00:37 GMT
> There is too much topics for one bug report. I'm paid by resolved bug report, more you open, more I'll get money.

Is that serious? If it is I will consider in the future and break down bugs. Do other maintainers also get paid for more for resolving more bugs? Is that secret information? (seems abusable)

> From what I tested, even if we don't flush the whole ruleset it will break docker.

May I know what and how you tested? Docker does not use nftables but iptables. (I need to test the iptables-nft with it) I've put docker in the report because I did not know the application that also uses nftables and is not firewall. (now I know sshguard)


I think I might know why Docker breaks. It might be the table name collision. If Docker uses a table named "filter" which is exact same name as nftables.conf uses. The https://wiki.archlinux.org/index.php/Nftables#Troubleshooting directs you to do that that.


> UDP 67 is not open for bootp/dhcp v4. Why should we allow dhcp by default?


I am NOT talking about server (who gives out IP addresses) but client (who recieves dns information and address).

Right now DHCP client for IPv4 will work but not for IPv6. This is because in DHCP v4 client establishes connection to server but in DHCPv6 server establishes connection to client.

One of the goals for nftables is to simplify dual stack configurations. (https://wiki.nftables.org/wiki-nftables/index.php/Why_nftables%3F) I think consistency is important part of being simple.

The current configuration makes IPv4/IPv6 behavior inconsistent.

It can be made consistent by disallowing any DHCP by blocking outbound port 53. This will make the network only function with statically assigned address and dns information. I don't think thats very practical especially for firewall that supposed to work in most situations.

Or it can be made function for both IPv4 and IPv6. The rule I created is actually very conservative. It only allows to receive on port 546 from link local address meaning its not open to global internet.

Actually it can be made even more secure:

ip6 daddr fe80::/64 ip6 saddr fe80::/64 udp sport 547 udp dport 546 ct state { new, untracked } accept

In case your router does not do egress filtering this rule will block the packets from global internet addressed to link local interface.

> Don't find the reference in wiki. Looks like bike-shedding.

It right there at the top in the Template:Style. I attached the screenshot from my browser.

It was added by the wiki admin Nl6720 on December 8th 2019 https://wiki.archlinux.org/index.php?title=Nftables&type=revision&diff=591240&oldid=591236

He also confirmed this on discussion page: https://wiki.archlinux.org/index.php/Talk:Nftables#syntax_improvements
Comment by Sébastien Luttringer (seblu) - Tuesday, 21 January 2020, 04:45 GMT
> Bug splitting
No, I was in a joking mood. Unfortunately no developer is paid. That's only for pleasure.

> Docker
With recent kernel, both iptables and nftables can "works" concurrently. But unfortunately, the way docker is using iptables doesn't allow such behavior. At least, I didn't succeed. So currently, my docker firewalling is disabled.

> DHCP
I'm not talking about the server either. There is no such protocol change between DHCP v4 and v6. It's still the client which request an IP to the server. You should read the comments from Pavel Šimerda in the StackExchange link you pointed, its assumption is mine too. To make it consistent between v4 and v6, the kernel should be fixed.

The funny side, is that Router Advertisement for SLAAC is coming from a "server" and it's bypassing the firewall.

> Variable name
Ok I see it. The idea is to mimic the iptables names. So, it will remain as it is.



Comment by igo95862 (igo95862) - Tuesday, 21 January 2020, 11:35 GMT
> DHCP


I searched a little bit more and I think it might be caused by some dhcp client using PF_PACKET socket for IPv4 connections but not for IPv6. PF_PACKET sockets ignore netfilter. See https://forums.centos.org/viewtopic.php?t=8728 and https://unix.stackexchange.com/questions/447440/ufw-iptables-not-blocking-dhcp-udp-port-67


I think I will play around with some containers and DHCP clients and servers, look through NetworkManager code... I think silently allowing any packet on 67/udp is a big security risk.


> The funny side, is that Router Advertisement for SLAAC is coming from a "server" and it's bypassing the firewall.

SLAAC uses ICMPv6.

Loading...