Skip to content

Commit

Permalink
net: ipv4: fix schedule while atomic bug in check_lifetime()
Browse files Browse the repository at this point in the history
move might_sleep operations out of the rcu_read_lock() section.
Also fix iterating over ifa_dev->ifa_list

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

Signed-off-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Jiri Pirko authored and David S. Miller committed Apr 8, 2013
1 parent 05a324b commit c988d1e
Showing 1 changed file with 42 additions and 16 deletions.
58 changes: 42 additions & 16 deletions net/ipv4/devinet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -625,19 +619,51 @@ 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)) {
next = ifa->ifa_tstamp +
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();
}
rcu_read_unlock();

next_sec = round_jiffies_up(next);
next_sched = next;
Expand Down

0 comments on commit c988d1e

Please sign in to comment.