nfs_common: fix localio to cope with racing nfs_local_probe()

Fix the possibility of racing nfs_local_probe() resulting in:
  list_add double add: new=ffff8b99707f9f58, prev=ffff8b99707f9f58, next=ffffffffc0f30000.
  ------------[ cut here ]------------
  kernel BUG at lib/list_debug.c:35!

Add nfs_uuid_init() to properly initialize all nfs_uuid_t members
(particularly its list_head).

Switch to returning bool from nfs_uuid_begin(), returns false if
nfs_uuid_t is already in-use (its list_head is on a list). Update
nfs_local_probe() to return early if the nfs_client's cl_uuid
(nfs_uuid_t) is in-use.

Also, switch nfs_uuid_begin() from using list_add_tail_rcu() to
list_add_tail() -- rculist was used in an earlier version of the
localio code that had a lockless nfs_uuid_lookup interface.

Signed-off-by: Mike Snitzer <snitzer@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
This commit is contained in:
Mike Snitzer 2024-10-23 16:34:42 -04:00 committed by Anna Schumaker
parent 40f45ab381
commit bc29408695
4 changed files with 23 additions and 9 deletions

View File

@ -181,8 +181,7 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
#if IS_ENABLED(CONFIG_NFS_LOCALIO) #if IS_ENABLED(CONFIG_NFS_LOCALIO)
seqlock_init(&clp->cl_boot_lock); seqlock_init(&clp->cl_boot_lock);
ktime_get_real_ts64(&clp->cl_nfssvc_boot); ktime_get_real_ts64(&clp->cl_nfssvc_boot);
clp->cl_uuid.net = NULL; nfs_uuid_init(&clp->cl_uuid);
clp->cl_uuid.dom = NULL;
spin_lock_init(&clp->cl_localio_lock); spin_lock_init(&clp->cl_localio_lock);
#endif /* CONFIG_NFS_LOCALIO */ #endif /* CONFIG_NFS_LOCALIO */

View File

@ -205,7 +205,8 @@ void nfs_local_probe(struct nfs_client *clp)
nfs_local_disable(clp); nfs_local_disable(clp);
} }
nfs_uuid_begin(&clp->cl_uuid); if (!nfs_uuid_begin(&clp->cl_uuid))
return;
if (nfs_server_uuid_is_local(clp)) if (nfs_server_uuid_is_local(clp))
nfs_local_enable(clp); nfs_local_enable(clp);
nfs_uuid_end(&clp->cl_uuid); nfs_uuid_end(&clp->cl_uuid);

View File

@ -5,7 +5,7 @@
*/ */
#include <linux/module.h> #include <linux/module.h>
#include <linux/rculist.h> #include <linux/list.h>
#include <linux/nfslocalio.h> #include <linux/nfslocalio.h>
#include <net/netns/generic.h> #include <net/netns/generic.h>
@ -20,15 +20,27 @@ static DEFINE_SPINLOCK(nfs_uuid_lock);
*/ */
static LIST_HEAD(nfs_uuids); static LIST_HEAD(nfs_uuids);
void nfs_uuid_begin(nfs_uuid_t *nfs_uuid) void nfs_uuid_init(nfs_uuid_t *nfs_uuid)
{ {
nfs_uuid->net = NULL; nfs_uuid->net = NULL;
nfs_uuid->dom = NULL; nfs_uuid->dom = NULL;
uuid_gen(&nfs_uuid->uuid); INIT_LIST_HEAD(&nfs_uuid->list);
}
EXPORT_SYMBOL_GPL(nfs_uuid_init);
bool nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
{
spin_lock(&nfs_uuid_lock); spin_lock(&nfs_uuid_lock);
list_add_tail_rcu(&nfs_uuid->list, &nfs_uuids); /* Is this nfs_uuid already in use? */
if (!list_empty(&nfs_uuid->list)) {
spin_unlock(&nfs_uuid_lock);
return false;
}
uuid_gen(&nfs_uuid->uuid);
list_add_tail(&nfs_uuid->list, &nfs_uuids);
spin_unlock(&nfs_uuid_lock); spin_unlock(&nfs_uuid_lock);
return true;
} }
EXPORT_SYMBOL_GPL(nfs_uuid_begin); EXPORT_SYMBOL_GPL(nfs_uuid_begin);
@ -36,7 +48,8 @@ void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
{ {
if (nfs_uuid->net == NULL) { if (nfs_uuid->net == NULL) {
spin_lock(&nfs_uuid_lock); spin_lock(&nfs_uuid_lock);
list_del_init(&nfs_uuid->list); if (nfs_uuid->net == NULL)
list_del_init(&nfs_uuid->list);
spin_unlock(&nfs_uuid_lock); spin_unlock(&nfs_uuid_lock);
} }
} }

View File

@ -32,7 +32,8 @@ typedef struct {
struct auth_domain *dom; /* auth_domain for localio */ struct auth_domain *dom; /* auth_domain for localio */
} nfs_uuid_t; } nfs_uuid_t;
void nfs_uuid_begin(nfs_uuid_t *); void nfs_uuid_init(nfs_uuid_t *);
bool nfs_uuid_begin(nfs_uuid_t *);
void nfs_uuid_end(nfs_uuid_t *); void nfs_uuid_end(nfs_uuid_t *);
void nfs_uuid_is_local(const uuid_t *, struct list_head *, void nfs_uuid_is_local(const uuid_t *, struct list_head *,
struct net *, struct auth_domain *, struct module *); struct net *, struct auth_domain *, struct module *);