]> git.ipfire.org Git - thirdparty/ipset.git/commitdiff
netfilter: ipset: Rework long task execution when adding/deleting entries
authorJozsef Kadlecsik <kadlec@netfilter.org>
Fri, 30 Dec 2022 11:32:37 +0000 (12:32 +0100)
committerJozsef Kadlecsik <kadlec@netfilter.org>
Fri, 30 Dec 2022 11:32:37 +0000 (12:32 +0100)
When adding/deleting large number of elements in one step in ipset, it can
take a reasonable amount of time and can result in soft lockup errors. The
patch 5f7b51bf09ba ("netfilter: ipset: Limit the maximal range of
consecutive elements to add/delete") tried to fix it by limiting the max
elements to process at all. However it was not enough, it is still possible
that we get hung tasks. Lowering the limit is not reasonable, so the
approach in this patch is as follows: rely on the method used at resizing
sets and save the state when we reach a smaller internal batch limit,
unlock/lock and proceed from the saved state. Thus we can avoid long
continuous tasks and at the same time removed the limit to add/delete large
number of elements in one step.

The nfnl mutex is held during the whole operation which prevents one to issue
other ipset commands in parallel.

Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Reported-by: syzbot+9204e7399656300bf271@syzkaller.appspotmail.com
Fixes: 5f7b51bf09ba ("netfilter: ipset: Limit the maximal range of consecutive elements to add/delete")
kernel/include/linux/netfilter/ipset/ip_set.h
kernel/net/netfilter/ipset/ip_set_core.c
kernel/net/netfilter/ipset/ip_set_hash_ip.c
kernel/net/netfilter/ipset/ip_set_hash_ipmark.c
kernel/net/netfilter/ipset/ip_set_hash_ipport.c
kernel/net/netfilter/ipset/ip_set_hash_ipportip.c
kernel/net/netfilter/ipset/ip_set_hash_ipportnet.c
kernel/net/netfilter/ipset/ip_set_hash_net.c
kernel/net/netfilter/ipset/ip_set_hash_netiface.c
kernel/net/netfilter/ipset/ip_set_hash_netnet.c
kernel/net/netfilter/ipset/ip_set_hash_netport.c

index efc0e6df11051e672373d732d98ea3e5a6f1776b..0c6eee2528e5a2efcdfd188097d34ed74c23301b 100644 (file)
@@ -200,7 +200,7 @@ struct ip_set_region {
 };
 
 /* Max range where every element is added/deleted in one step */
-#define IPSET_MAX_RANGE                (1<<20)
+#define IPSET_MAX_RANGE                (1<<14)
 
 /* The max revision number supported by any set type + 1 */
 #define IPSET_REVISION_MAX     9
index 77fb55cd4bace77ad74d51349f20d8627dddbae5..a80b9996f8f62f5d06350adc018a8b92d6f2fc63 100644 (file)
@@ -1754,9 +1754,10 @@ CALL_AD(struct net *net, struct sock *ctnl, struct sk_buff *skb,
                ret = set->variant->uadt(set, tb, adt, &lineno, flags, retried);
                ip_set_unlock(set);
                retried = true;
-       } while (ret == -EAGAIN &&
-                set->variant->resize &&
-                (ret = set->variant->resize(set, retried)) == 0);
+       } while (ret == -ERANGE ||
+                (ret == -EAGAIN &&
+                 set->variant->resize &&
+                 (ret = set->variant->resize(set, retried)) == 0));
 
        if (!ret || (ret == -IPSET_ERR_EXIST && eexist))
                return 0;
index be11354b8b9acfbb8d68eef2c11618c27eed7242..5a2cb7105d75ced62b8e497beeba77e1c2dcad21 100644 (file)
@@ -101,11 +101,11 @@ static int
 hash_ip4_uadt(struct ip_set *set, struct nlattr *tb[],
              enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_ip4 *h = set->data;
+       struct hash_ip4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_ip4_elem e = { 0 };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip = 0, ip_to = 0, hosts;
+       u32 ip = 0, ip_to = 0, hosts, i = 0;
        int ret = 0;
 
        if (tb[IPSET_ATTR_LINENO])
@@ -150,14 +150,14 @@ hash_ip4_uadt(struct ip_set *set, struct nlattr *tb[],
 
        hosts = h->netmask == 32 ? 1 : 2 << (32 - h->netmask - 1);
 
-       /* 64bit division is not allowed on 32bit */
-       if (((u64)ip_to - ip + 1) >> (32 - h->netmask) > IPSET_MAX_RANGE)
-               return -ERANGE;
-
        if (retried)
                ip = ntohl(h->next.ip);
-       for (; ip <= ip_to;) {
+       for (; ip <= ip_to; i++) {
                e.ip = htonl(ip);
+               if (i > IPSET_MAX_RANGE) {
+                       hash_ip4_data_next(&h->next, &e);
+                       return -ERANGE;
+               }
                ret = adtfn(set, &e, &ext, &ext, flags);
                if (ret && !ip_set_eexist(ret, flags))
                        return ret;
index 7e7eedef45621251ef569344f189ecac8bea5883..ad2a2dcb30cf8a6eeb5729646944bd2d8bcc35e1 100644 (file)
@@ -99,11 +99,11 @@ static int
 hash_ipmark4_uadt(struct ip_set *set, struct nlattr *tb[],
                  enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_ipmark4 *h = set->data;
+       struct hash_ipmark4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_ipmark4_elem e = { };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip, ip_to = 0;
+       u32 ip, ip_to = 0, i = 0;
        int ret;
 
        if (tb[IPSET_ATTR_LINENO])
@@ -150,13 +150,14 @@ hash_ipmark4_uadt(struct ip_set *set, struct nlattr *tb[],
                ip_set_mask_from_to(ip, ip_to, cidr);
        }
 
-       if (((u64)ip_to - ip + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
-
        if (retried)
                ip = ntohl(h->next.ip);
-       for (; ip <= ip_to; ip++) {
+       for (; ip <= ip_to; ip++, i++) {
                e.ip = htonl(ip);
+               if (i > IPSET_MAX_RANGE) {
+                       hash_ipmark4_data_next(&h->next, &e);
+                       return -ERANGE;
+               }
                ret = adtfn(set, &e, &ext, &ext, flags);
 
                if (ret && !ip_set_eexist(ret, flags))
index 5ca5df9456fbdb5d70685707d435beba222fa3ae..46f8bbfbf9b5c31535a9ce6d8da60867a0fd0e92 100644 (file)
@@ -113,11 +113,11 @@ static int
 hash_ipport4_uadt(struct ip_set *set, struct nlattr *tb[],
                  enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_ipport4 *h = set->data;
+       struct hash_ipport4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_ipport4_elem e = { .ip = 0 };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip, ip_to = 0, p = 0, port, port_to;
+       u32 ip, ip_to = 0, p = 0, port, port_to, i = 0;
        bool with_ports = false;
        int ret;
 
@@ -185,17 +185,18 @@ hash_ipport4_uadt(struct ip_set *set, struct nlattr *tb[],
                        swap(port, port_to);
        }
 
-       if (((u64)ip_to - ip + 1)*(port_to - port + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
-
        if (retried)
                ip = ntohl(h->next.ip);
        for (; ip <= ip_to; ip++) {
                p = retried && ip == ntohl(h->next.ip) ? ntohs(h->next.port)
                                                       : port;
-               for (; p <= port_to; p++) {
+               for (; p <= port_to; p++, i++) {
                        e.ip = htonl(ip);
                        e.port = htons(p);
+                       if (i > IPSET_MAX_RANGE) {
+                               hash_ipport4_data_next(&h->next, &e);
+                               return -ERANGE;
+                       }
                        ret = adtfn(set, &e, &ext, &ext, flags);
 
                        if (ret && !ip_set_eexist(ret, flags))
index 0be8f5337042d201c41308f0a79136fc0d49bd18..ca5fe9c2934b72dbb82ed9da1708833aca482b76 100644 (file)
@@ -109,11 +109,11 @@ static int
 hash_ipportip4_uadt(struct ip_set *set, struct nlattr *tb[],
                    enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_ipportip4 *h = set->data;
+       struct hash_ipportip4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_ipportip4_elem e = { .ip = 0 };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip, ip_to = 0, p = 0, port, port_to;
+       u32 ip, ip_to = 0, p = 0, port, port_to, i = 0;
        bool with_ports = false;
        int ret;
 
@@ -181,17 +181,18 @@ hash_ipportip4_uadt(struct ip_set *set, struct nlattr *tb[],
                        swap(port, port_to);
        }
 
-       if (((u64)ip_to - ip + 1)*(port_to - port + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
-
        if (retried)
                ip = ntohl(h->next.ip);
        for (; ip <= ip_to; ip++) {
                p = retried && ip == ntohl(h->next.ip) ? ntohs(h->next.port)
                                                       : port;
-               for (; p <= port_to; p++) {
+               for (; p <= port_to; p++, i++) {
                        e.ip = htonl(ip);
                        e.port = htons(p);
+                       if (i > IPSET_MAX_RANGE) {
+                               hash_ipportip4_data_next(&h->next, &e);
+                               return -ERANGE;
+                       }
                        ret = adtfn(set, &e, &ext, &ext, flags);
 
                        if (ret && !ip_set_eexist(ret, flags))
index 8f3c06995992b0e90873cc3a20e9d1f0f4e28fbb..8f44644496b41476731dec1763cf6b160c4a8c37 100644 (file)
@@ -161,12 +161,12 @@ static int
 hash_ipportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                     enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_ipportnet4 *h = set->data;
+       struct hash_ipportnet4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_ipportnet4_elem e = { .cidr = HOST_MASK - 1 };
        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;
+       u32 ip2_from = 0, ip2_to = 0, ip2, i = 0;
        bool with_ports = false;
        u8 cidr;
        int ret;
@@ -254,9 +254,6 @@ hash_ipportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                        swap(port, port_to);
        }
 
-       if (((u64)ip_to - ip + 1)*(port_to - port + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
-
        ip2_to = ip2_from;
        if (tb[IPSET_ATTR_IP2_TO]) {
                ret = ip_set_get_hostipaddr4(tb[IPSET_ATTR_IP2_TO], &ip2_to);
@@ -283,9 +280,15 @@ hash_ipportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                for (; p <= port_to; p++) {
                        e.port = htons(p);
                        do {
+                               i++;
                                e.ip2 = htonl(ip2);
                                ip2 = ip_set_range_to_cidr(ip2, ip2_to, &cidr);
                                e.cidr = cidr - 1;
+                               if (i > IPSET_MAX_RANGE) {
+                                       hash_ipportnet4_data_next(&h->next,
+                                                                 &e);
+                                       return -ERANGE;
+                               }
                                ret = adtfn(set, &e, &ext, &ext, flags);
 
                                if (ret && !ip_set_eexist(ret, flags))
index 5dfd0eddab714e4f98172cdc74fd68d70fe46bd7..8baceea7d454fe62e3927dcffc74c8747857f1d5 100644 (file)
@@ -137,11 +137,11 @@ static int
 hash_net4_uadt(struct ip_set *set, struct nlattr *tb[],
               enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_net4 *h = set->data;
+       struct hash_net4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_net4_elem e = { .cidr = HOST_MASK };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip = 0, ip_to = 0, ipn, n = 0;
+       u32 ip = 0, ip_to = 0, i = 0;
        int ret;
 
        if (tb[IPSET_ATTR_LINENO])
@@ -189,19 +189,16 @@ hash_net4_uadt(struct ip_set *set, struct nlattr *tb[],
                if (ip + UINT_MAX == ip_to)
                        return -IPSET_ERR_HASH_RANGE;
        }
-       ipn = ip;
-       do {
-               ipn = ip_set_range_to_cidr(ipn, ip_to, &e.cidr);
-               n++;
-       } while (ipn++ < ip_to);
-
-       if (n > IPSET_MAX_RANGE)
-               return -ERANGE;
 
        if (retried)
                ip = ntohl(h->next.ip);
        do {
+               i++;
                e.ip = htonl(ip);
+               if (i > IPSET_MAX_RANGE) {
+                       hash_net4_data_next(&h->next, &e);
+                       return -ERANGE;
+               }
                ip = ip_set_range_to_cidr(ip, ip_to, &e.cidr);
                ret = adtfn(set, &e, &ext, &ext, flags);
                if (ret && !ip_set_eexist(ret, flags))
index b25400a2c1785a2159216193db3b2425c7047de3..af210c7da4632f1a11de7c38c00b6ea536879645 100644 (file)
@@ -203,7 +203,7 @@ hash_netiface4_uadt(struct ip_set *set, struct nlattr *tb[],
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_netiface4_elem e = { .cidr = HOST_MASK, .elem = 1 };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 ip = 0, ip_to = 0, ipn, n = 0;
+       u32 ip = 0, ip_to = 0, i = 0;
        int ret;
 
        if (tb[IPSET_ATTR_LINENO])
@@ -257,19 +257,16 @@ hash_netiface4_uadt(struct ip_set *set, struct nlattr *tb[],
        } else {
                ip_set_mask_from_to(ip, ip_to, e.cidr);
        }
-       ipn = ip;
-       do {
-               ipn = ip_set_range_to_cidr(ipn, ip_to, &e.cidr);
-               n++;
-       } while (ipn++ < ip_to);
-
-       if (n > IPSET_MAX_RANGE)
-               return -ERANGE;
 
        if (retried)
                ip = ntohl(h->next.ip);
        do {
+               i++;
                e.ip = htonl(ip);
+               if (i > IPSET_MAX_RANGE) {
+                       hash_netiface4_data_next(&h->next, &e);
+                       return -ERANGE;
+               }
                ip = ip_set_range_to_cidr(ip, ip_to, &e.cidr);
                ret = adtfn(set, &e, &ext, &ext, flags);
 
index cdfb78c6e0d3de56488cebc5a34df470a7e5ed36..8fbe649c9dd3d48691655e2155f94da8e0f16b2a 100644 (file)
@@ -166,13 +166,12 @@ static int
 hash_netnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                  enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_netnet4 *h = set->data;
+       struct hash_netnet4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_netnet4_elem e = { };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
        u32 ip = 0, ip_to = 0;
-       u32 ip2 = 0, ip2_from = 0, ip2_to = 0, ipn;
-       u64 n = 0, m = 0;
+       u32 ip2 = 0, ip2_from = 0, ip2_to = 0, i = 0;
        int ret;
 
        if (tb[IPSET_ATTR_LINENO])
@@ -248,19 +247,6 @@ hash_netnet4_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 > IPSET_MAX_RANGE)
-               return -ERANGE;
 
        if (retried) {
                ip = ntohl(h->next.ip[0]);
@@ -273,7 +259,12 @@ hash_netnet4_uadt(struct ip_set *set, struct nlattr *tb[],
                e.ip[0] = htonl(ip);
                ip = ip_set_range_to_cidr(ip, ip_to, &e.cidr[0]);
                do {
+                       i++;
                        e.ip[1] = htonl(ip2);
+                       if (i > IPSET_MAX_RANGE) {
+                               hash_netnet4_data_next(&h->next, &e);
+                               return -ERANGE;
+                       }
                        ip2 = ip_set_range_to_cidr(ip2, ip2_to, &e.cidr[1]);
                        ret = adtfn(set, &e, &ext, &ext, flags);
                        if (ret && !ip_set_eexist(ret, flags))
index cf703249d83c75e8254e7b4bbe8bde906968f619..8a2a7c52efc6105dd05a21b72af4e5f755e6aaa6 100644 (file)
@@ -155,12 +155,11 @@ static int
 hash_netport4_uadt(struct ip_set *set, struct nlattr *tb[],
                   enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
 {
-       const struct hash_netport4 *h = set->data;
+       struct hash_netport4 *h = set->data;
        ipset_adtfn adtfn = set->variant->adt[adt];
        struct hash_netport4_elem e = { .cidr = HOST_MASK - 1 };
        struct ip_set_ext ext = IP_SET_INIT_UEXT(set);
-       u32 port, port_to, p = 0, ip = 0, ip_to = 0, ipn;
-       u64 n = 0;
+       u32 port, port_to, p = 0, ip = 0, ip_to = 0, i = 0;
        bool with_ports = false;
        u8 cidr;
        int ret;
@@ -237,14 +236,6 @@ hash_netport4_uadt(struct ip_set *set, struct nlattr *tb[],
        } else {
                ip_set_mask_from_to(ip, ip_to, e.cidr + 1);
        }
-       ipn = ip;
-       do {
-               ipn = ip_set_range_to_cidr(ipn, ip_to, &cidr);
-               n++;
-       } while (ipn++ < ip_to);
-
-       if (n*(port_to - port + 1) > IPSET_MAX_RANGE)
-               return -ERANGE;
 
        if (retried) {
                ip = ntohl(h->next.ip);
@@ -256,8 +247,12 @@ hash_netport4_uadt(struct ip_set *set, struct nlattr *tb[],
                e.ip = htonl(ip);
                ip = ip_set_range_to_cidr(ip, ip_to, &cidr);
                e.cidr = cidr - 1;
-               for (; p <= port_to; p++) {
+               for (; p <= port_to; p++, i++) {
                        e.port = htons(p);
+                       if (i > IPSET_MAX_RANGE) {
+                               hash_netport4_data_next(&h->next, &e);
+                               return -ERANGE;
+                       }
                        ret = adtfn(set, &e, &ext, &ext, flags);
                        if (ret && !ip_set_eexist(ret, flags))
                                return ret;