QubesOS / qubes-issues

The Qubes OS Project issue tracker
https://www.qubes-os.org/doc/issue-tracking/
536 stars 47 forks source link

Qubes firewall is blocking DNATed connections to Virtual DNS #8765

Open 1cho1ce opened 10 months ago

1cho1ce commented 10 months ago

Qubes OS release

R4.2

Brief summary

I wanted to create a netvm with blocked access to my LAN and use it as netvm for other qubes in this setup:

sys-net <-> sys-no-lan <-> my-qube-without-lan-access

But if I add drop rule for my LAN addresses to sys-no-lan firewall rules like this:

qvm-firewall sys-no-lan add --before 0 match dsthost=192.168.1.0/24 action=drop

Then DNS is not working for sys-no-lan and my-qube-without-lan-access because after DNAT in prerouting it'll be blocked by LAN block rule in qubes-firewall:

table ip qubes {
...
    chain dnat-dns {
        type nat hook prerouting priority dstnat; policy accept;
        ip daddr 10.139.1.1 udp dport 53 dnat to 192.168.1.1
        ip daddr 10.139.1.1 tcp dport 53 dnat to 192.168.1.1
    }
...
}

table ip qubes-firewall {
    chain forward {
        type filter hook forward priority filter; policy drop;
        ct state established,related accept
        iifname != "vif*" accept
        ip saddr 10.138.3.11 jump qbs-10-138-3-11
    }
...
    chain qbs-10-138-3-11 {
        ip daddr 192.168.1.0/24 reject with icmp admin-prohibited
        accept
        reject with icmp admin-prohibited
    }
}

I understand that the correct Qubes-way would be using additional sys-firewall like this:

sys-net <-> sys-firewall <-> sys-no-lan <-> my-qube-without-lan-access

And then I wouldn't have this issue but I'm reluctant to waste resources for additional firewall qube and I'm accepting the possible risks of using firewall in sys-net.

The possible fix to this issue is to add ct status dnat accept rule like this:

table ip qubes-firewall {
    chain forward {
        type filter hook forward priority filter; policy drop;
        ct state established,related accept
        ct status dnat accept
        iifname != "vif*" accept
        ip saddr 10.138.3.11 jump qbs-10-138-3-11
    }
...
}

Should I make a pull request for this or are there any drawbacks to allowing dnated connections?

Minimalist73 commented 10 months ago

I guess this is to be expected since you block your whole lan. I don't use a local address for DNS so I can't test myself, but can't you add the following rule before the drop to allow DNS requests on lan?

qvm-firewall sys-no-lan add --before 0 dsthost=192.168.1.1 specialtarget=dns action=accept
1cho1ce commented 10 months ago

It'll work this way as well. But to be precise adding allow rule to 192.168.1.1:53 is not the same as allowing DNATed connections to Qubes OS virtual DNS. For example, with this rule some software running in the my-qube-without-lan-access connected to sys-no-lan can use 192.168.1.1:53 DNS directly without going through virtual DNS. That can leak your LAN DNS server address. I'm not sure how bad this could be but it makes a difference. Also it's inconsistent with how it works with another sys-firewall in between where you don't need to add this rule explicitly.

marmarek commented 10 months ago

There is indeed a difference but that's how the firewall works - it's about the final address when it gets redirected, not the original one. Skipping sys-firewall and trying to enforce firewall sys-net is not the best idea, as sys-net is exposed to many more attacks, so it's not surprising you hit this corner case.

Anyway, what happens if you reload firewall for this qube (qvm-firewall -r sys-no-lan) after LAN connection gets established? Theoretically it should get resolved to the actual DNS server.

1cho1ce commented 10 months ago

Skipping sys-firewall and trying to enforce firewall sys-net is not the best idea, as sys-net is exposed to many more attacks, so it's not surprising you hit this corner case.

Yes, I understand the risks. But this issues is true not only for sys-net but for sys-vpn as well and it's not as risky to enforce firewall rules in sys-vpn.

Anyway, what happens if you reload firewall for this qube (qvm-firewall -r sys-no-lan) after LAN connection gets established? Theoretically it should get resolved to the actual DNS server.

I don't think reloading firewall should change anything here. I've tested it just to be sure and it didn't change anything. Maybe I don't understand something but what could have changed by this?

marmarek commented 10 months ago

specialtarget=dns is resolved at firewall (re)load time, based on /etc/resolv.conf.

marmarek commented 10 months ago

Ah, wait, you have dsthost=192.168.1.0/24 action=drop before specialtarget=dns action=accept, right? If so, you instructed firewall to drop all traffic to 192.168.1.0/24, before accepting traffic to the DNS server. It did exactly that and works as intended in that case.

1cho1ce commented 10 months ago

specialtarget=dns is not used here since I'm blacklisting the LAN addresses using qvm-firewall and not whitelisting:

[user@dom0 ~]$ qvm-firewall sys-no-lan add --before 0 match dsthost=192.168.1.0/24 action=drop
[user@dom0 ~]$ qvm-firewall -r sys-no-lan
NO  ACTION  HOST            PROTOCOL  PORT(S)  SPECIAL TARGET  ICMP TYPE  EXPIRE  COMMENT
0   drop    192.168.1.0/24  -         -        -               -          -       -
1   accept  -               -         -        -               -          -       -
1cho1ce commented 10 months ago

It did exactly that and works as intended in that case.

It's true that it works as intended but it works inconsistently with how it works in this setup:

sys-net <-> sys-firewall <-> sys-no-lan <-> my-qube-without-lan-access

Where connections to 192.168.1.1:53 are not blocked for sys-no-lan because they are DNATed after sys-firewall in sys-net. And sys-firewall see these connections as connections to virtual DNS servers and not to 192.168.1.1:53.

So I wanted to have a default rule that will allow connections in firewall qube that were DNATed in the same firewall qube as well so it'll work the same way as it work with additional firewall qube.

marmarek commented 10 months ago

I see what you mean, but that isn't how firewall works. The DNAT happens before "filter" table so it doesn't see the original address. There are likely workarounds that could handle this case (fwmark?) but is a corner case I don't consider important enough to handle. If you or somebody else would like to work on it and contribute a patch (preferably with a matching test), I'll likely accept it, tough.

1cho1ce commented 10 months ago

There are likely workarounds that could handle this case (fwmark?) but is a corner case I don't consider important enough to handle.

It can be done with ct status dnat accept rule, I've tested it and it works as I wanted: https://wiki.nftables.org/wiki-nftables/index.php/Matching_connection_tracking_stateful_metainformation#ct_status_-_conntrack_status

IPS_DST_NAT dnat Set when connection needs DNAT in original dirction.

So allowing connections with ct status dnat will allow the DNATed connections to virtual DNS servers. But if there will be some other custom DNAT rules in firewall qube besides Qubes OS default virtual DNS DNAT rules then these connections will be allowed as well. Not sure if it could lead to something bad if users are unaware of this rule. This rule could be more specific if I add DNS destination port to it so it'll only allow DNATed DNS connections:

ct status dnat meta l4proto {tcp, udp} th dport 53 accept

It's still not perfect because there could be some other custom DNS dnat rules but I can't think up of a better solution.

If you or somebody else would like to work on it and contribute a patch (preferably with a matching test), I'll likely accept it, tough.

If this is acceptable then I'll start working on PR.

marmarek commented 10 months ago

Yes, it sounds acceptable for specialtarget=dns rule to consider also DNAT state. Then if you put specialtarget=dns accept rule before the drop, it theoretically won't allow direct traffic to the DNS IP, only redirected one.

As for the tests, firewall is tested here: https://github.com/QubesOS/qubes-core-admin/blob/main/qubes/tests/integ/network.py#L243 - you can extend this one, or write a new one (based on this one). You can find documentation on testing at https://www.qubes-os.org/doc/automated-tests/

1cho1ce commented 10 months ago

Ok, I'll start working on it. Thanks for the tips about tests, I'll will need some time to get acquainted with it first.