There are a few places where we iterate over the IPv6 address list
without taking the address list lock, I assume because of some
difficulties in doing so without causing lock recursion. As a result,
it's trivial to trigger a kernel panic by adding or removing IPv6
addresses concurrently. This change attempts to address the common
instances of this problem.
The main change is to nd6_timer(), which iterates over the address list
looking for expired addresses. It calls in6_purgeaddr() on such
addresses, which requires taking the address list write lock to remove
the address' entry. It may also call regen_tmpaddr() to generate a new
temporary address to replace an expired address, which also requires
taking the write lock.
A straightforward solution is to just take the write lock around the
loop and adjust in6_purgeaddr and regen_tmpaddr to avoid recursion,
perhaps by adding _locked variants. This gets somewhat hairy though, and
introduces the potential for lock contention: nd6_timer() is called once
a second by default, and the vast majority of invocations likely won't
need the write lock. Moreover, the read lock is taken in some forwarding
paths (via in6_localip()). My solution involves two changes: add a
"to-purge" list to nd6_timer(), and have the loop insert expired
addresses into it. Once we're done scanning the whole list, call
in6_purgeaddr() on each address in the to-purge list, so that we don't
take the write lock more than necessary. This requires adding a field to
in6_ifaddr; to compensate, I removed the unused ia_plen (which was
taking up 8 bytes on amd64 because of padding). The second part of this
change modifies regen_tmpaddr(): on entry, it now expects the read lock
to be held and drops it iff a new address is added, in which case it
returns unlocked. This asymmetry is a bit ugly, but regen_tmpaddr() is
static and only called from nd6_timer(), so I think it's a reasonable
compromise.
I also modified in6_unlink_ifa to fix a double free in the case that two
threads race to remove the same address: once we've acquired the in6 addr
write lock, check to see if the address has already been removed and
return early if so.