]> git.dujemihanovic.xyz Git - linux.git/commitdiff
netfilter: ipset: Rework long task execution when adding/deleting entries
authorJozsef Kadlecsik <kadlec@netfilter.org>
Fri, 30 Dec 2022 12:24:38 +0000 (13:24 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 2 Jan 2023 14:10:05 +0000 (15:10 +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.

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

index ab934ad951a8706a2ee228ab7fb70c35f9260328..e8c350a3ade153d852bec011dbd3c72a352d319d 100644 (file)
@@ -197,7 +197,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 e7ba5b6dd2b7c1792e76723f68553f8d174e1ae9..46ebee9400dab83ef9a14a55bbfb1d2bd68005d4 100644 (file)
@@ -1698,9 +1698,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 e30513cefd90e729394c3f5e235462b7b681ff41..c9f4e38596632338695e783cc7fb70ab12cd94d6 100644 (file)
@@ -100,11 +100,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])
@@ -149,14 +149,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 153de3457423e58601f309bb797da33cc1a40229..a22ec1a6f6ec85b1c7c3cf04a824e02a1c74057c 100644 (file)
@@ -97,11 +97,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])
@@ -148,13 +148,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 2ffbd0b78a8c529495fa334bf17223808cad9692..e977b5a9c48dcb23c8d89f78f7e6c0ae2b3e266a 100644 (file)
@@ -112,11 +112,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;
 
@@ -184,17 +184,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 334fb1ad0e86cfca13fa5c3e69b5ae84ffc0fc90..39a01934b1536d800b14a6cddc4175cf64126ee7 100644 (file)
@@ -108,11 +108,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;
 
@@ -180,17 +180,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 7df94f437f600f98e48a62dcfb3078b8b01da6e2..5c6de605a9fb7fd13db6b3497685d39d47a25911 100644 (file)
@@ -160,12 +160,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;
@@ -253,9 +253,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);
@@ -282,9 +279,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 1422739d9aa255fb0b7620b7057eabc137b00d84..ce0a9ce5a91f1a08ec8ceb666cee3979fc5bddc3 100644 (file)
@@ -136,11 +136,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])
@@ -188,19 +188,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 9810f5bf63f5e42d5715d0419049c0c097f2e587..0310732862362c68a805fa4d7648b616c9ffc102 100644 (file)
@@ -202,7 +202,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])
@@ -256,19 +256,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 09cf72eb37f8d2549b3527530f1e4ca9e7d10efa..d1a0628df4ef3a7301a8e972874106dc135176fe 100644 (file)
@@ -154,12 +154,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;
@@ -236,14 +235,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);
@@ -255,8 +246,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;