Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c988d1e8 authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller
Browse files

net: ipv4: fix schedule while atomic bug in check_lifetime()



move might_sleep operations out of the rcu_read_lock() section.
Also fix iterating over ifa_dev->ifa_list

Introduced by: commit 5c766d64 "ipv4: introduce address lifetime"

Signed-off-by: default avatarJiri Pirko <jiri@resnulli.us>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 05a324b9
Loading
Loading
Loading
Loading
+42 −16
Original line number Diff line number Diff line
@@ -587,13 +587,16 @@ static void check_lifetime(struct work_struct *work)
{
	unsigned long now, next, next_sec, next_sched;
	struct in_ifaddr *ifa;
	struct hlist_node *n;
	int i;

	now = jiffies;
	next = round_jiffies_up(now + ADDR_CHECK_FREQUENCY);

	rcu_read_lock();
	for (i = 0; i < IN4_ADDR_HSIZE; i++) {
		bool change_needed = false;

		rcu_read_lock();
		hlist_for_each_entry_rcu(ifa, &inet_addr_lst[i], hash) {
			unsigned long age;

@@ -606,16 +609,7 @@ static void check_lifetime(struct work_struct *work)

			if (ifa->ifa_valid_lft != INFINITY_LIFE_TIME &&
			    age >= ifa->ifa_valid_lft) {
				struct in_ifaddr **ifap ;

				rtnl_lock();
				for (ifap = &ifa->ifa_dev->ifa_list;
				     *ifap != NULL; ifap = &ifa->ifa_next) {
					if (*ifap == ifa)
						inet_del_ifa(ifa->ifa_dev,
							     ifap, 1);
				}
				rtnl_unlock();
				change_needed = true;
			} else if (ifa->ifa_preferred_lft ==
				   INFINITY_LIFE_TIME) {
				continue;
@@ -625,10 +619,8 @@ static void check_lifetime(struct work_struct *work)
					next = ifa->ifa_tstamp +
					       ifa->ifa_valid_lft * HZ;

				if (!(ifa->ifa_flags & IFA_F_DEPRECATED)) {
					ifa->ifa_flags |= IFA_F_DEPRECATED;
					rtmsg_ifa(RTM_NEWADDR, ifa, NULL, 0);
				}
				if (!(ifa->ifa_flags & IFA_F_DEPRECATED))
					change_needed = true;
			} else if (time_before(ifa->ifa_tstamp +
					       ifa->ifa_preferred_lft * HZ,
					       next)) {
@@ -636,8 +628,42 @@ static void check_lifetime(struct work_struct *work)
				       ifa->ifa_preferred_lft * HZ;
			}
		}
	}
		rcu_read_unlock();
		if (!change_needed)
			continue;
		rtnl_lock();
		hlist_for_each_entry_safe(ifa, n, &inet_addr_lst[i], hash) {
			unsigned long age;

			if (ifa->ifa_flags & IFA_F_PERMANENT)
				continue;

			/* We try to batch several events at once. */
			age = (now - ifa->ifa_tstamp +
			       ADDRCONF_TIMER_FUZZ_MINUS) / HZ;

			if (ifa->ifa_valid_lft != INFINITY_LIFE_TIME &&
			    age >= ifa->ifa_valid_lft) {
				struct in_ifaddr **ifap;

				for (ifap = &ifa->ifa_dev->ifa_list;
				     *ifap != NULL; ifap = &(*ifap)->ifa_next) {
					if (*ifap == ifa) {
						inet_del_ifa(ifa->ifa_dev,
							     ifap, 1);
						break;
					}
				}
			} else if (ifa->ifa_preferred_lft !=
				   INFINITY_LIFE_TIME &&
				   age >= ifa->ifa_preferred_lft &&
				   !(ifa->ifa_flags & IFA_F_DEPRECATED)) {
				ifa->ifa_flags |= IFA_F_DEPRECATED;
				rtmsg_ifa(RTM_NEWADDR, ifa, NULL, 0);
			}
		}
		rtnl_unlock();
	}

	next_sec = round_jiffies_up(next);
	next_sched = next;