Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: ipv4: fix one memleak in __inet_del_ifa()

I got the below warning when do fuzzing test:
unregister_netdevice: waiting for bond0 to become free. Usage count = 2

It can be repoduced via:

ip link add bond0 type bond
sysctl -w net.ipv4.conf.bond0.promote_secondaries=1
ip addr add 4.117.174.103/0 scope 0x40 dev bond0
ip addr add 192.168.100.111/255.255.255.254 scope 0 dev bond0
ip addr add 0.0.0.4/0 scope 0x40 secondary dev bond0
ip addr del 4.117.174.103/0 scope 0x40 dev bond0
ip link delete bond0 type bond

In this reproduction test case, an incorrect 'last_prim' is found in
__inet_del_ifa(), as a result, the secondary address(0.0.0.4/0 scope 0x40)
is lost. The memory of the secondary address is leaked and the reference of
in_device and net_device is leaked.

Fix this problem:
Look for 'last_prim' starting at location of the deleted IP and inserting
the promoted IP into the location of 'last_prim'.

Fixes: 0ff60a45678e ("[IPV4]: Fix secondary IP addresses after promotion")
Signed-off-by: Liu Jian <liujian56@huawei.com>
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Liu Jian and committed by
David S. Miller
ac28b1ec 73be7fb1

+5 -5
+5 -5
net/ipv4/devinet.c
··· 355 355 { 356 356 struct in_ifaddr *promote = NULL; 357 357 struct in_ifaddr *ifa, *ifa1; 358 - struct in_ifaddr *last_prim; 358 + struct in_ifaddr __rcu **last_prim; 359 359 struct in_ifaddr *prev_prom = NULL; 360 360 int do_promote = IN_DEV_PROMOTE_SECONDARIES(in_dev); 361 361 362 362 ASSERT_RTNL(); 363 363 364 364 ifa1 = rtnl_dereference(*ifap); 365 - last_prim = rtnl_dereference(in_dev->ifa_list); 365 + last_prim = ifap; 366 366 if (in_dev->dead) 367 367 goto no_promotions; 368 368 ··· 376 376 while ((ifa = rtnl_dereference(*ifap1)) != NULL) { 377 377 if (!(ifa->ifa_flags & IFA_F_SECONDARY) && 378 378 ifa1->ifa_scope <= ifa->ifa_scope) 379 - last_prim = ifa; 379 + last_prim = &ifa->ifa_next; 380 380 381 381 if (!(ifa->ifa_flags & IFA_F_SECONDARY) || 382 382 ifa1->ifa_mask != ifa->ifa_mask || ··· 440 440 441 441 rcu_assign_pointer(prev_prom->ifa_next, next_sec); 442 442 443 - last_sec = rtnl_dereference(last_prim->ifa_next); 443 + last_sec = rtnl_dereference(*last_prim); 444 444 rcu_assign_pointer(promote->ifa_next, last_sec); 445 - rcu_assign_pointer(last_prim->ifa_next, promote); 445 + rcu_assign_pointer(*last_prim, promote); 446 446 } 447 447 448 448 promote->ifa_flags &= ~IFA_F_SECONDARY;