netfilter: ipset: fix hash:net,port,net hang with /0 subnet
authorJozsef Kadlecsik <kadlec@netfilter.org>
Fri, 30 Dec 2022 12:24:37 +0000 (13:24 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Jan 2023 10:59:15 +0000 (11:59 +0100)
[ Upstream commit a31d47be64b9b74f8cfedffe03e0a8a1f9e51f23 ]

The hash:net,port,net set type supports /0 subnets. However, the patch
commit 5f7b51bf09baca8e titled "netfilter: ipset: Limit the maximal range
of consecutive elements to add/delete" did not take into account it and
resulted in an endless loop. The bug is actually older but the patch
5f7b51bf09baca8e brings it out earlier.

Handle /0 subnets properly in hash:net,port,net set types.

Fixes: 5f7b51bf09ba ("netfilter: ipset: Limit the maximal range of consecutive elements to add/delete")
Reported-by: Марк Коренберг <socketpair@gmail.com>
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/netfilter/ipset/ip_set_hash_netportnet.c

index 19bcdb3..005a7ce 100644 (file)
@@ -173,17 +173,26 @@ hash_netportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
        return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
 }
 
+static u32
+hash_netportnet4_range_to_cidr(u32 from, u32 to, u8 *cidr)
+{
+       if (from == 0 && to == UINT_MAX) {
+               *cidr = 0;
+               return to;
+       }
+       return ip_set_range_to_cidr(from, to, cidr);
+}
+
 static int
 hash_netportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                      enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_netportnet4 *h = set->data;
+       struct hash_netportnet4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_netportnet4_elem e = { };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
        u32 ip = 0, ip_to = 0, p = 0, port, port_to;
-       u32 ip2_from = 0, ip2_to = 0, ip2, ipn;
-       u64 n = 0, m = 0;
+       u32 ip2_from = 0, ip2_to = 0, ip2, i = 0;
        bool with_ports = false;
        int ret;
 
@@ -285,19 +294,6 @@ hash_netportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
        } else {
                ip_set_mask_from_to(ip2_from, ip2_to, e.cidr[1]);
        }
-       ipn = ip;
-       do {
-               ipn = ip_set_range_to_cidr(ipn, ip_to, &e.cidr[0]);
-               n++;
-       } while (ipn++ < ip_to);
-       ipn = ip2_from;
-       do {
-               ipn = ip_set_range_to_cidr(ipn, ip2_to, &e.cidr[1]);
-               m++;
-       } while (ipn++ < ip2_to);
-
-       if (n*m*(port_to - port + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
 
        if (retried) {
                ip = ntohl(h->next.ip[0]);
@@ -310,13 +306,19 @@ hash_netportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
 
        do {
                e.ip[0] = htonl(ip);
-               ip = ip_set_range_to_cidr(ip, ip_to, &e.cidr[0]);
+               ip = hash_netportnet4_range_to_cidr(ip, ip_to, &e.cidr[0]);
                for (; p <= port_to; p++) {
                        e.port = htons(p);
                        do {
+                               i++;
                                e.ip[1] = htonl(ip2);
-                               ip2 = ip_set_range_to_cidr(ip2, ip2_to,
-                                                          &e.cidr[1]);
+                               if (i > IPSET_MAX_RANGE) {
+                                       hash_netportnet4_data_next(&h->next,
+                                                                  &e);
+                                       return -ERANGE;
+                               }
+                               ip2 = hash_netportnet4_range_to_cidr(ip2,
+                                                       ip2_to, &e.cidr[1]);
                                ret = adtfn(set, &e, &ext, &ext, flags);
                                if (ret && !ip_set_eexist(ret, flags))
                                        return ret;