Commit 5e29dc36 authored by Jozsef Kadlecsik's avatar Jozsef Kadlecsik Committed by Pablo Neira Ayuso
Browse files

netfilter: ipset: Rework long task execution when adding/deleting entries



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 5f7b51bf ("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: 5f7b51bf ("netfilter: ipset: Limit the maximal range of consecutive elements to add/delete")
Reported-by: default avatar <syzbot+9204e7399656300bf271@syzkaller.appspotmail.com>
Signed-off-by: default avatarJozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent a31d47be
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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
+4 −3
Original line number Diff line number Diff line
@@ -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 &&
	} while (ret == -ERANGE ||
		 (ret == -EAGAIN &&
		  set->variant->resize &&
		 (ret = set->variant->resize(set, retried)) == 0);
		  (ret = set->variant->resize(set, retried)) == 0));

	if (!ret || (ret == -IPSET_ERR_EXIST && eexist))
		return 0;
+7 −7
Original line number Diff line number Diff line
@@ -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;
+7 −6
Original line number Diff line number Diff line
@@ -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))
+7 −6
Original line number Diff line number Diff line
@@ -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))
Loading