From: Yang Erkun <yangerkun@xxxxxxxxxx>
shell:
mkfs.xfs -f /dev/sda
echo "/ *(rw,no_root_squash,fsid=0)" > /etc/exports
echo "/mnt *(rw,no_root_squash,fsid=1)" >> /etc/exports
exportfs -ra
service nfs-server start
mount -t nfs -o vers=4.0 127.0.0.1:/mnt /mnt1
mount /dev/sda /mnt/sda
touch /mnt1/sda/file
exportfs -r
umount /mnt/sda
Commit f8c989a0c89a ("nfsd: release svc_expkey/svc_export with
rcu_work")
describe that when we call e_show/c_show, the last reference can down to
0, and we will call expkey_put/svc_export_put with rcu context, this may
lead uaf or sleep in atomic bug. Finally, we introduce async mode to the
release and fix the bug. However, some other command may also finally
call
expkey_put/svc_export_put without rcu context, but expect that the sync
mode for the resource release. Like upper shell, before that commit,
exportfs -r will remove all entry with sync mode, and the last umount
/mnt/sda will always success. But after this commit, the umount will
always
fail, after we add some delay, they will success again. Personally, I
think
is actually a bug, and need be fixed.
Use rcu_read_lock_any_held to distinguish does we really under rcu
context,
and if no, release resource with sync mode.
Fixes: f8c989a0c89a ("nfsd: release svc_expkey/svc_export with
rcu_work")
Signed-off-by: Yang Erkun <yangerkun@xxxxxxxxxx>
---
fs/nfsd/export.c | 44 ++++++++++++++++++++++++++++++++------------
1 file changed, 32 insertions(+), 12 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index eacafe46e3b6..25f13e877c2f 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -40,11 +40,8 @@
#define EXPKEY_HASHMAX (1 << EXPKEY_HASHBITS)
#define EXPKEY_HASHMASK (EXPKEY_HASHMAX -1)
-static void expkey_put_work(struct work_struct *work)
+static void expkey_release(struct svc_expkey *key)
{
- struct svc_expkey *key =
- container_of(to_rcu_work(work), struct svc_expkey,
ek_rcu_work);
-
if (test_bit(CACHE_VALID, &key->h.flags) &&
!test_bit(CACHE_NEGATIVE, &key->h.flags))
path_put(&key->ek_path);
@@ -52,12 +49,25 @@ static void expkey_put_work(struct work_struct
*work)
kfree(key);
}
+static void expkey_put_work(struct work_struct *work)
+{
+ struct svc_expkey *key =
+ container_of(to_rcu_work(work), struct svc_expkey,
ek_rcu_work);
+
+ expkey_release(key);
+}
+
static void expkey_put(struct kref *ref)
{
struct svc_expkey *key = container_of(ref, struct svc_expkey,
h.ref);
- INIT_RCU_WORK(&key->ek_rcu_work, expkey_put_work);
- queue_rcu_work(system_wq, &key->ek_rcu_work);
+ if (rcu_read_lock_any_held()) {