From nobody Sun May 24 19:33:21 2026 Received: from mail-qt1-f176.google.com (mail-qt1-f176.google.com [209.85.160.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5EA9425B09A for ; Sat, 23 May 2026 01:41:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779500476; cv=none; b=kVAoAIUm6eyL/Ar9u7yOuUwb/4FY0PU3JNtbAXVNuiDhiuJgKOtPoaLDo1DjbLAQNY8I/XGxAQwAnNNYdiAHDWlqVQFDSK3O8JkOHy5NRod32MMYVSD2DPSHGtCEAVMiL3lYtgyaASb633NF20tCDobdr3A5gVVGETka7u0h+R8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779500476; c=relaxed/simple; bh=NKmTmNXZNUVsawrzyHgxo886ekn2OAXS4D0rn9JgprE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=FPl7EwG8sC1gm0+gm+hGHbLjJx2+soXUaSc2cnZrcHQTqedjNvfxHh3aQ++yXCJxNwQGbLdoi0KHa/p++3DQcoLOKoi2a6vxIDEPSDVB0Up8m7/BIqbC8Ct8CQ1bFTfesB7EEXSYyH8YK+dkl6LqON0GHps7XLuh/L9t/f9/Wnc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ivusw1vd; arc=none smtp.client-ip=209.85.160.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ivusw1vd" Received: by mail-qt1-f176.google.com with SMTP id d75a77b69052e-51306c9f2a8so93582981cf.1 for ; Fri, 22 May 2026 18:41:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779500474; x=1780105274; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=NznA/de/GNUvCfqwNzu7QISNK1Z1Xgcm290xHganHVE=; b=ivusw1vdQ5H+u5YVsniLG9/Z3lk6ixweGutLAPmpZFPTeKGnAaI8fnLH/SECIPcCg1 MBtEj1OSrQS7on/laAe2GdQEtLcs2FVkEaoczNzTXsXjfaQsokYfEtIVsCy7YPin/q9Y vHPAQb2QJcqew5zeT7YYpZa7oOtikVsakMNxwlXyWDfHvJ6vZS5vdMGaZzPZEbZZobHD qr+lBf5iXM3VbsiBDZ4/Vd6fap2rQVct59IyciA+J+0N1myGcFQe/8zL6putrUUvFluY 6RQFDUr4dOsIZN5hFPX2AmXZ7YjkzIOeqXABXy21Q+xNNmRIVqg3Lm+p1HuQfvns+Hqu ABSA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779500474; x=1780105274; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=NznA/de/GNUvCfqwNzu7QISNK1Z1Xgcm290xHganHVE=; b=V/sMjSLIyGY5Jq0zgnZ1x2DP6B7ZfiWXmuQQ1xwUtWhJS3L03CMFTsya/F6N4BbxAg W2lj5vmx4RSLFQJyC8pxehxdN3/EtZdtBgTQ0CcZmpdOFOxLukNboW2uo7NdReJ+8ktZ d/BiiS+Pd4+a0IciHhCD/VLfp0CAJWfQQh5PWvWkFpjg2msiCT6bV2Z3ncODjzYantKP o3nx88wcTYAS3LasB5EWVlDdreZ3vSLGapQoSVb8qjCt7jxx3tCjYTqdUlqXDrpIiuY2 A26fBH6C6nVJpYYWLDsfxSjK13issRl3U9/DDLLOUB95AqMQljl1wtoh0HMPWqa9K9Ym RmNw== X-Forwarded-Encrypted: i=1; AFNElJ/24h8GCp0wsoYhxq82kh/zZfhO2PX5D/4SBQSGoOhk9/qBxPxohbizFGp5N/C6BDzj6WZ+oc8Ra/GWiS0=@vger.kernel.org X-Gm-Message-State: AOJu0YwN2tgZJQxBXqYsmXWAAqS/mbKxbCa2FiIa+Uc8Ev73oA4eoJOO FMELk9loSW0sl2JqrYrVdQyKkR6adFWhwJ0ezrJwpl1jagKUAzpbA8P3eIFU2DBZ0Xw= X-Gm-Gg: Acq92OGruGVTh96kJmKvkaXEMQZd3RrCLzJKjyhTek5NagNyo7JJhy4Xp06tj3EYLHq AiUQTbJgKZdOBpBNbmAzHWWCHVCWAuJ+WktHngnLonISG4kYgtZVefbhEMpG45IU1DCkKUvbOOp J+ucY3el5eMdyJJxFLWYr+vleuj2L/7kIZ0VJwjC5jMLL2dbrQRU1Ws2BSQwszNmGuucWX3URcx igIQUIll9NVOPwDeyVHPL5ZrX0noMRwBW4RU/oJduWnwFzgL7Yw196cEZNXGwEwvzT5uNrKwMjZ vgE/WyFeiUCn7gcCZ58QFfrX766rxCoqDFPtZ68KWTUdkIG4323r1t7wXOMDoz/INjNLMZSDga4 bexc/zftJkokuxGiR2NwFTQUj1mB45pTQhfrnaTVdoMjt0r79ZD5Dy9cljJp3w95YwXyKLTS28Z Z0CgwdVElC72l34Tm3E8Yerod87E0YPOq6E7dQayiRDGlDCV+mf7sIF5En6gLi15JAlq7IXW0Yp ow4gGrRJicQo2UfnJHz9yeXbWHVhnY= X-Received: by 2002:a05:622a:5906:b0:50f:b9e6:e058 with SMTP id d75a77b69052e-516d444cb4dmr90035591cf.25.1779500474184; Fri, 22 May 2026 18:41:14 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-516d8b24cecsm32445651cf.9.2026.05.22.18.41.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 22 May 2026 18:41:13 -0700 (PDT) From: Michael Bommarito To: Chuck Lever , Jeff Layton Cc: NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH] NFSD: restart ssc_expire_umount walk after dropping nfsd_ssc_lock Date: Fri, 22 May 2026 21:41:07 -0400 Message-ID: <20260523014107.2460863-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" nfsd4_ssc_expire_umount() walks nn->nfsd_ssc_mount_list with list_for_each_entry_safe(ni, tmp, ...). For each expired entry it sets nsui_busy =3D true, drops nfsd_ssc_lock to run mntput() on the source vfsmount, then reacquires the lock to list_del + kfree the entry and continue iterating via the macro's saved tmp pointer. The nsui_busy flag protects the current ni from concurrent nfsd4_ssc_setup_dul() finders during the lock-drop window, but it does not pin tmp. Another nfsd RPC thread that fails its source- server mount and reaches nfsd4_ssc_cancel_dul() will, during that same window, take nfsd_ssc_lock, list_del + kfree its own ssc_umount item, and release the lock. If that item is the saved tmp of the expire walk, the next iteration dereferences a freed nfsd4_ssc_umount_item. Reachability: triggered by any authenticated NFSv4.2 client that can issue OP_COPY with cna_src.nl4_type =3D NL4_SERVER to a destination nfsd built with CONFIG_NFSD_V4_2_INTER_SSC=3Dy and started with inter_copy_offload_enable=3DY. The client chooses the source-server netaddr and can pick one that fails vfs_kern_mount() (unreachable, RST after EXCHANGE_ID, etc.) to drive nfsd4_ssc_cancel_dul() into the laundromat's lock-drop window. Default Linux nfsd ships with inter_copy_offload_enable=3DN, so the bug is reachable only on servers where the administrator has explicitly opted into inter-SSC offload. Restart the walk from the head after the mntput() unlock window so no saved next pointer survives the lock-drop. The list is bounded by the number of active inter-server source mounts (typically small) and the expire delayed-work runs periodically rather than per-IO, so the restart is cheap. Cc: stable@vger.kernel.org Fixes: f4e44b393389 ("NFSD: delay unmount source's export after inter-serve= r copy completed.") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Reviewed-by: Jeff Layton --- fs/nfsd/nfs4state.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) Reproduced under QEMU/KVM with KASAN, three nfsd network namespaces on a single host so the kernel client treats them as distinct servers, and Linux fault injection forcing vfs_kern_mount allocations inside the destination nfsd to fail. This drives nfsd4_ssc_cancel_dul into a tight loop concurrent with the laundromat workqueue. Stock kernel: BUG: KASAN: slab-use-after-free in laundromat_main+0x1756/0x1be0 Read of size 8 at addr ffff88800ce9b200 by task kworker/u16:3 Workqueue: nfsd4 laundromat_main Allocated by task 229: nfsd4_interssc_connect+0x3f5/0xd90 (nfsd4_ssc_setup_dul, inlined) nfsd4_copy+0x117d/0x1a30 nfsd4_proc_compound+0xbe9/0x23f0 Freed by task 229: kfree+0x18f/0x520 nfsd4_interssc_connect+0xaff/0xd90 (nfsd4_ssc_cancel_dul, inlined) nfsd4_copy+0x117d/0x1a30 The buggy address belongs to the cache kmalloc-128 of size 128. Kernel panic - not syncing: Fatal exception Patched kernel ran the equivalent workload to completion with the inter-SSC code path exercised 21-22 times per run and no KASAN report. The fault-injection knobs are standard Linux testing infrastructure (see Documentation/fault-injection/) exercising the existing failure path in nfsd; no kernel source was modified. The same primitive class was previously addressed by the OPEN-error path fix in __nfs42_ssc_open(); this patch closes the corresponding hole in the laundromat-driven delayed-unmount path. diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 6b9c399b89dfb..03582f15e3e7e 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -6728,30 +6728,37 @@ static void nfsd4_ssc_shutdown_umount(struct nfsd_n= et *nn) static void nfsd4_ssc_expire_umount(struct nfsd_net *nn) { bool do_wakeup =3D false; - struct nfsd4_ssc_umount_item *ni =3D NULL; - struct nfsd4_ssc_umount_item *tmp; + struct nfsd4_ssc_umount_item *ni; =20 +restart: spin_lock(&nn->nfsd_ssc_lock); - list_for_each_entry_safe(ni, tmp, &nn->nfsd_ssc_mount_list, nsui_list) { - if (time_after(jiffies, ni->nsui_expire)) { - if (refcount_read(&ni->nsui_refcnt) > 1) - continue; + list_for_each_entry(ni, &nn->nfsd_ssc_mount_list, nsui_list) { + if (!time_after(jiffies, ni->nsui_expire)) + break; + if (refcount_read(&ni->nsui_refcnt) > 1) + continue; =20 - /* mark being unmount */ - ni->nsui_busy =3D true; - spin_unlock(&nn->nfsd_ssc_lock); - mntput(ni->nsui_vfsmount); - spin_lock(&nn->nfsd_ssc_lock); + /* mark being unmount */ + ni->nsui_busy =3D true; + spin_unlock(&nn->nfsd_ssc_lock); + mntput(ni->nsui_vfsmount); + spin_lock(&nn->nfsd_ssc_lock); =20 - /* waiters need to start from begin of list */ - list_del(&ni->nsui_list); - kfree(ni); + /* waiters need to start from begin of list */ + list_del(&ni->nsui_list); + kfree(ni); =20 - /* wakeup ssc_connect waiters */ - do_wakeup =3D true; - continue; - } - break; + /* wakeup ssc_connect waiters */ + do_wakeup =3D true; + /* + * The list_for_each_entry_safe() saved-next pointer was + * not pinned across the spin_unlock() above: a concurrent + * nfsd4_ssc_cancel_dul() can free the next item under the + * same spinlock while mntput() runs. Restart the walk + * from the head so no stale next is dereferenced. + */ + spin_unlock(&nn->nfsd_ssc_lock); + goto restart; } if (do_wakeup) wake_up_all(&nn->nfsd_ssc_waitq); --=20 2.53.0