batman-adv: tt: queue local DEL event under bucket lock

batadv_tt_local_remove() sets BATADV_TT_CLIENT_PENDING on an already
announced local entry and only afterwards queues the DEL change event. It
holds neither the hash bucket list_lock nor bat_priv->tt.commit_lock.

It can therefore be potentially interrupted in the middle:

  CPU0                          CPU1
  batadv_tt_local_remove()
    flags |= ..._PENDING;
                                batadv_tt_local_commit_changes()
                                  ..._purge_pending_clients()
                                    hlist_del_rcu(&...->hash_entry);
                                  batadv_tt_local_update_crc()
                                  atomic_inc(&bat_priv->tt.vn);
    batadv_tt_local_event()
    /* DEL queued only now */

The client then disappears from the local table and from the CRC of the new
TTVN after batadv_tt_local_commit_changes() without a DEL change being
announced for it. Neighbours receiving the new CRC without previously
seeing the DEL will try to recover via a full table request.

Move the event into batadv_tt_local_mark_removed() and hold the bucket
list_lock of the entry around both the flag change and the DEL event to
avoid this scenario.

Fixes: 976b159b3c ("batman-adv: tt: use protected flag modifications")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831135117.574836-1-sw%40simonwunderlich.de?part=12
Signed-off-by: Sven Eckelmann <sven@narfation.org>
This commit is contained in:
Sven Eckelmann 2026-09-01 21:07:56 +02:00
parent 597c0f2f69
commit f95b9f28a9
No known key found for this signature in database
GPG Key ID: 4D0F772BD314F5CB

View File

@ -1427,13 +1427,20 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb)
* @message: debug message describing the reason for the change
*
* Schedule the TT change announcement for the entry. The caller must already
* have added BATADV_TT_CLIENT_PENDING to the @tt_local_entry
* have added BATADV_TT_CLIENT_PENDING to the @tt_local_entry and must hold the
* hash bucket list_lock of @tt_local_entry since setting the flag.
*/
static void
batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv,
struct batadv_tt_local_entry *tt_local_entry,
u16 flags, const char *message)
{
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
u32 i;
i = batadv_choose_tt(&tt_local_entry->common, hash->size);
lockdep_assert_held(&hash->list_locks[i]);
batadv_tt_local_event(bat_priv, tt_local_entry, flags);
batadv_dbg(BATADV_DBG_TT, bat_priv,
@ -1443,20 +1450,37 @@ batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv,
}
/**
* batadv_tt_local_mark_removed() - mark a local entry as removed
* batadv_tt_local_mark_removed() - mark a local entry as removed and queue DEL
* @bat_priv: the bat priv with all the mesh interface information
* @tt_local_entry: local TT entry to mark
* @message: message to append to the log on deletion
* @roaming: true if the deletion is due to a roaming event
* @curr_flags: pointer to store the flags of the entry before it was marked
*
* An already announced entry is marked as BATADV_TT_CLIENT_PENDING and the
* (roamed) DEL change is queued. Both happen under the hash bucket list_lock
* of the entry to prevent concurrent batadv_tt_local_purge_pending_clients()
* from removing the entry.
*
* Return: true if the entry has to be kept in the local table until the next
* ttvn increment, false if it can be purged immediately.
*/
static bool
batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry,
bool roaming, u16 *curr_flags)
batadv_tt_local_mark_removed(struct batadv_priv *bat_priv,
struct batadv_tt_local_entry *tt_local_entry,
const char *message, bool roaming, u16 *curr_flags)
{
spinlock_t *list_lock; /* protects write access to the hash lists */
struct batadv_tt_common_entry *common = &tt_local_entry->common;
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
bool pending = false;
u16 flags;
u32 i;
i = batadv_choose_tt(common, hash->size);
list_lock = &hash->list_locks[i];
spin_lock_bh(list_lock);
scoped_guard(spinlock_bh, &common->flags_lock) {
*curr_flags = common->flags;
@ -1474,6 +1498,17 @@ batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry,
}
}
if (pending) {
flags = BATADV_TT_CLIENT_DEL;
if (roaming)
flags |= BATADV_TT_CLIENT_ROAM;
batadv_tt_local_set_pending_event(bat_priv, tt_local_entry,
flags, message);
}
spin_unlock_bh(list_lock);
return pending;
}
@ -1532,28 +1567,17 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr,
{
struct batadv_tt_local_entry *tt_local_entry;
u16 curr_flags;
u16 flags;
tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid);
if (!tt_local_entry)
return BATADV_NO_FLAGS;
if (batadv_tt_local_mark_removed(tt_local_entry, roaming, &curr_flags)) {
/* queue (roamed) del event which was prepared by
* batadv_tt_local_mark_removed()
*/
flags = BATADV_TT_CLIENT_DEL;
if (roaming)
flags |= BATADV_TT_CLIENT_ROAM;
batadv_tt_local_set_pending_event(bat_priv, tt_local_entry,
flags, message);
} else {
/* if this client has been added right now, it is possible to
* immediately purge it
*/
/* if this client has been added right now, it is possible to
* immediately purge it
*/
if (!batadv_tt_local_mark_removed(bat_priv, tt_local_entry, message,
roaming, &curr_flags))
batadv_tt_local_remove_now(bat_priv, tt_local_entry);
}
batadv_tt_local_entry_put(tt_local_entry);