From nobody Sat Sep 26 04:29:50 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 803FE495030; Fri, 4 Sep 2026 14:17:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531467; cv=none; b=iIVuZoFTbXPV65vpSz9tcfoXPlZDl2NlhocejaqZU/NZiB6N56I/+R6RAgezJ9AVKg+yKnry6tJXQzlVFsbNPKwIWeUXwO4qR4uxfvRX3pAd+lPvU+fb4dcDa3BxTvFw/XKVwQDDi3SLzJnKkMoyncr7XLiYLehSHOdF1vT7J+M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531467; c=relaxed/simple; bh=GPuj3sulN1EVqegKGOkxmLt9iqOchOeoF/C189BSDLQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Npt1jB1izvFCZxwYkmH9qc9ouC0s4JGsocRrF7Q5fHF4zsgPJshWJSjYe94qUW5vw5jc3dFngU3WSEPi6qvw1UdKKUU8pG4yqq7o8hXDjQJ9ss0gXg57WiZ867RICTOnXBPOB58pnXpDm7fwhwLq1aVBdy4r7zCRzksNLpsPvsk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=YbKVV9BS; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="YbKVV9BS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1C00B1F00ACA; Fri, 4 Sep 2026 14:17:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788531465; bh=dzfQvEvrcjHdupca1CqJWp8dWvoMvwWFsdUd3DQS658=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=YbKVV9BS8nDsCLwuwYVbN2UtZwwZjXLhctDIaD+rVI51pLBZ/UotRb6IO9cBzVSkX HwE6ZuQS5TDzzohFnyR6mflzVUelicYV3QtijBUxmRaT4LFqcT/JGQfYhrVVL0evVZ EznCjYkxsH9g+3dgDlQpo6jkJ7fNtvPh5DRPE6ffVpmAzGex8+dEQwQNroJUgzv0Bf zzoIweN1riletZA5M2XiKEyzwdOpzcc6j5CMSlRPTfKxVlUY57/iCiUm10i/6OXxu1 vcSNglYA1+HK0lxsss5mo0A6vaHM2G5jB46xIa8Nr1Zd6CMgSlFV/2iXBjxH5Z047Q Gmz+PpMctEw/g== From: Jeff Layton Date: Fri, 04 Sep 2026 10:17:32 -0400 Subject: [PATCH v3 1/3] fs: stamp the current time for a stale delegated ctime update Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260904-delegts-v3-1-b6062ba75f07@kernel.org> References: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> In-Reply-To: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> To: Christian Brauner , Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Alexander Viro , Jan Kara Cc: Thomas Haynes , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2831; i=jlayton@kernel.org; h=from:subject:message-id; bh=GPuj3sulN1EVqegKGOkxmLt9iqOchOeoF/C189BSDLQ=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqmtMGXMNBhYUyiUAW+NXCgYGnfZMVRvnNgWj2y GNQcUdFiNCJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCaprTBgAKCRAADmhBGVaC FVo+EADU6a38WWjDxoqGsoPkLgXaSVAI5RfMCUAR0F2uapbDOZs6pG3m/nMGDx1tS/QI8ysT2zp TP+DIDhgnEyxJnqmDLVZsHjU4ZU5UkcBzWbQY4LM/lDRsBwRkPw3MHB6NfpUaMrtAsY/Zk8SYgq ooZ8GXLR6K/q1QEqbQfcETzpTIdSzl36a/6hZI8TA5BcLHTkI4Un+eteWOPtTlXlpaWce+cglHY 3VgNcOp1BeSSYFYjoLb2ktNXGGhkgltwavH0BrUCzyTKaaNNlWMrwgYrA5Pm+Lc9r2fVNHDPxsr Otr4PtC9KcliSFuorz/HqeMEbh5h1VifTltfQikE3lE8Ut2T00+1w5AliJ0TcptcF0Iq++9d5ZA aFXeUACQZyJZztuoTDqZCGVchfWKEY/0J/PYa9cYDW2FeWx3Itde5Fp8BvG2YokW4SWlW/InK5/ dtUBkX73j9HOl156iaAn8uiO1j+4gkpcjh3NkNHJ1oaEvocEyGwtjzh0Ef6vUXjXCZLvj/6TFuD Gl9LloqP1Pa5UoJ9467RxOmy1ZddHJ11aWTAPNK1pRnpxQweOCpeS5lHNnoomBvr432c//vkhCG mUjiO+tgIL6SBgX/zYSqBXxdiKmbL8OmB0Hm5ejNU3tE6A5hOswS2gxIAgzUzAJlLux85sRfO7v 42avkVUwBQ9fqFA== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 inode_set_ctime_deleg() drops an update that does not advance the ctime. That is correct when a client reports a timestamp that it advanced on its own. It is wrong when the client moves the timestamp backwards on purpose, as utimensat() does. Stamp the current time in that case. The ctime still never moves backwards. Fixes: 3952f1cbcbc4 ("nfsd: fix SETATTR updates for delegated timestamps") Assisted-by: LLM Reviewed-by: Jan Kara Acked-by: Chuck Lever Signed-off-by: Jeff Layton --- fs/inode.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a3..09eac2e0e172 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2959,11 +2959,17 @@ EXPORT_SYMBOL(inode_set_ctime_current); * inode attributes, including the mtime. When updating the mtime, update * the ctime to a value at least equal to that. * - * This can race with concurrent updates to the inode, in which - * case the update is skipped. + * The ctime never moves backwards. An @update that does not advance the c= time + * records the current time instead, so that the delegated change is still + * visible in the ctime. + * + * This can still race with a concurrent update to the inode. That stamp t= akes + * precedence, and is at least as recent as the one it displaces. * * Note that this works even when multigrain timestamps are not enabled, * so it is used in either case. + * + * Returns the resulting ctime. */ struct timespec64 inode_set_ctime_deleg(struct inode *inode, struct timesp= ec64 update) { @@ -2975,10 +2981,6 @@ struct timespec64 inode_set_ctime_deleg(struct inode= *inode, struct timespec64 u cur_ts.tv_nsec =3D cur & ~I_CTIME_QUERIED; cur_ts.tv_sec =3D inode_get_ctime_sec(inode); =20 - /* If the update is older than the existing value, skip it. */ - if (timespec64_compare(&update, &cur_ts) <=3D 0) - return cur_ts; - ktime_get_coarse_real_ts64_mg(&now); =20 /* Clamp the update to "now" if it's in the future */ @@ -2987,9 +2989,14 @@ struct timespec64 inode_set_ctime_deleg(struct inode= *inode, struct timespec64 u =20 update =3D timestamp_truncate(update, inode); =20 - /* No need to update if the values are already the same */ - if (timespec64_equal(&update, &cur_ts)) - return cur_ts; + /* + * The update does not advance the ctime. Stamp the current time, so + * that the delegated change is still visible in the ctime. Compare + * after clamping and truncating, since either can pull an update that + * was ahead of the ctime back onto it. + */ + if (timespec64_compare(&update, &cur_ts) <=3D 0) + return inode_set_ctime_current(inode); =20 /* * Try to swap the nsec value into place. If it fails, that means --=20 2.55.0 From nobody Sat Sep 26 04:29:50 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BC8124CA29A; Fri, 4 Sep 2026 14:17:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531468; cv=none; b=Hv6SNj43mVswCG5jjlpFktdghO5T9qLWJfkE+K1vQz+TikvTNEp29DsGYLLzQFuEb4WJbgzKt+jtCl7IpobluC2iWMsBCS2OEPMGANFrCDSHjDHbuwzWfg6hYftDz63FYHwVMISnNcgX9hwDon4MJBCTfJ81dTaGpU/UlxKbElo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531468; c=relaxed/simple; bh=8hRIrkXhsCHuH6/33Sbko533YgmaaqWhPLGLG9ilVQY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AKVjZkEf5emzf7f13fYs/cpVOCeXGleLksWae5/hW+rYnY+w0fZPL8nT4SyYN6Qc3G6u72kLU98GpJ+vgbcfJYz+mitfwhuD6KEWble82WvYHDHqqBMFs4OhUZt213O9MUFVh2zgPD3rK81daFnNL6ZkLr/M2bjTHUV4LH0iKRw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=MBLOsdgv; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="MBLOsdgv" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 48AEA1F00A3E; Fri, 4 Sep 2026 14:17:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788531466; bh=CKpbti+kDC+b0CxbSlKMt4z9dBXGZBkI8Ba7PnNyl9I=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=MBLOsdgvrrsyDt17ftt84VpXoYBtrys2vkF2o7WPE9KG9LpvA+9bpgD5JLwCq2WO2 AJ/7o4HeEzOf2jV8ciaV7TKOhzgzzot5DPBBlLdkW6mwvktJ64KBW5VA1oAJg2Kg8n O2HHEPw/zLTdkxX0NL7VL7LIC+t4qlimgS1XgTXogZwoSIjA7eGlUt4r5l10nTAm31 V9IJVoc+jmye3SpMGuNjEwYjqdt0GraGcYfHxXmoPwEZgpVmIKR2tFBk2T24Ry/fdp 7HVfFGEGR7LLH+Z/JJsqBxS4tHQtox2uwoHF8BaQn2Ox89BAvVajzRGG9enRcHjalh gycWaEkaLb3mQ== From: Jeff Layton Date: Fri, 04 Sep 2026 10:17:33 -0400 Subject: [PATCH v3 2/3] nfsd: accept a backdated timestamp from a delegation holder Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260904-delegts-v3-2-b6062ba75f07@kernel.org> References: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> In-Reply-To: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> To: Christian Brauner , Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Alexander Viro , Jan Kara Cc: Thomas Haynes , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9038; i=jlayton@kernel.org; h=from:subject:message-id; bh=8hRIrkXhsCHuH6/33Sbko533YgmaaqWhPLGLG9ilVQY=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqmtMGDSaVGepPXMZtNn0ZNA9q1ySp+NfFEPTeh hYLqg3T8pyJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCaprTBgAKCRAADmhBGVaC FRtgD/9xDWiIJg0jHDNKAxxjAuqmsAgvXYboe6kibACIxpl2LD+Se0tQtf2wms8asF0TZkyLdme ksrSxnsnn6QQYjLwg0l/EGxo7nTkeG9tQ2fCBj0TlNZbyXmn0VuxI5taEReHqfCEBe+JAASD43M 97iVsQ+3ODJHs3Fz6v6e6Yb26oBxlgtcQO5Vs4ninrcRJI+LSAl/Bf5dMO6s/cwiYMuR0J/O9Qj xZPCmWLSQxyNPW0nAZZ4V/ZVZRSES7Td77dhvCfQXUuxLWfiBbCPN73YwTGxFZQ3wnfF5dKF5+4 yLDKtJucJT5/oxe0hK+0eEPwHRKi/4jAz+AI+2rDP/ND3FWaKd6I3Xwvczfl6Sx73ZHx8k+vsRP YZkQ3K0dHfAo0fhtp/aPpAlqziOemXHZ2c3TMMESu8FJwI+3YYHYlCqbC7WTceIlCEbK0BZ3S8F e8J77E/r242EQ5CAXXeNpHpwyd2p/qaiRnhUn8PiHJ24G3ucSwLzwSr6KNt6UdkqgoD0VnyPFaf FJhJz0IR3Z3z509PLT20DE/XijQeBQ/QLWjEPuJgHzFN8h8Sx5elI/9K4L8QiPuAC1Z+aC5Bc12 CginHE9leOY5jI9gejTJLWoSGs9d3QyRbEuH+WAgT5DL1LuiDJwZN6RvDG7dWsqAsDMpSTAksGK 1UQoXBijWpTU5jw== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 A client with an attribute delegation reports the file times in a SETATTR at DELEGRETURN. nfsd ignores a time that moves backwards. If the file was written, nfsd4_finalize_deleg_timestamps() then stamps the c/mtime with the current time, so the file keeps the DELEGRETURN time. cp -p, rsync -t and tar -x lose timestamps. The SETATTR returns NFS4_OK. The client applies an explicit utimensat() to its own inode. It sends no SETATTR while it holds the delegation, so the backdated value reaches nfsd only as TIME_DELEG_MODIFY. The client can send an RPC for each time change instead. That also works, but it loses the caching that the delegation allows. The delegation makes the client the authority for these times, so treat its SETATTR as a statement of fact. The client can set the same value with an ordinary SETATTR, which nfsd applies without a check. - nfsd accepts a backwards atime or mtime. - An mtime that moves backwards sets the ctime to the current time. inode_set_ctime_deleg() does that, so nfsd does not compare against the ctime here. The ctime never moves backwards. - The client reports the times at every DELEGRETURN, changed or not. Drop a report that already matches the inode, and leave the ctime alone when neither the mtime nor the data moved. Otherwise an untouched file gets a new change attribute every time a delegation comes back and every other client drops its cache, notify_change() runs for nothing, and a holder that does not own the file gets -EPERM out of setattr_prepare() where it used to get NFS4_OK. - Compare against the inode before clamping. A file can carry a time in the future, set by an ordinary SETATTR, which nfsd does not clamp. Clamping first makes an unchanged report differ from the inode and drags the time back to "now". - dl_setattr stops nfsd4_finalize_deleg_timestamps() from stamping over the reported times, so only the branch that carries a c/mtime update may set it, and only once nfsd_setattr() has applied it. A write that follows a no-op SETATTR would otherwise lose its timestamps, and so would one that precedes a failed SETATTR. Hold the stateid reference until then. - nfsd still clamps a future time. - The CB_GETATTR path keeps the old rule. A backwards time there shows a stale report. RFC 9754 says that the server ignores a time before the original time. This patch does not follow that sentence. The same section also says that the server MUST accept the change or MUST reject it with NFS4ERR_DELAY. A silent discard does neither. A retry after NFS4ERR_DELAY carries the same backdated value, so that option cannot succeed. There is still one gap: nfsd cannot tell an explicit utimensat() from a report of a write. An mtime after the ctime and before the current time therefore sets the ctime to that mtime, instead of to "now". RFC 9754 requires this. Fixing that would require the client to issue an RPC for the mtime. Fixes: 3952f1cbcbc4 ("nfsd: fix SETATTR updates for delegated timestamps") Assisted-by: LLM Acked-by: Chuck Lever Signed-off-by: Jeff Layton --- fs/nfsd/nfs4proc.c | 92 ++++++++++++++++++++++++++++++++++++++++++--------= ---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index bb74eef43938..edab1144016a 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1292,32 +1292,69 @@ nfsd4_secinfo_no_name_release(union nfsd4_op_u *u) } =20 /* - * Validate that the requested timestamps are within the acceptable range.= If - * timestamp appears to be in the future, then it will be clamped to - * current_time(). + * A client holding a delegation with delegated timestamps is the authorit= y for + * the file's timestamps, so a SETATTR from it asserts what they are rathe= r than + * reporting that they have advanced. Honor a value that moves a timestamp + * backwards: the client could set the same value with an ordinary SETATTR= , so + * refusing it here only loses data. Clamp a value in the future to the cu= rrent + * time, as RFC 9754 permits. */ static void +clamp_deleg_time(struct timespec64 *req, const struct timespec64 *now) +{ + if (timespec64_compare(req, now) > 0) + *req =3D *now; +} + +/* + * Apply the timestamps that a delegation holder supplied in a SETATTR. + * + * Returns true if the request carries a c/mtime update, so that the calle= r can + * set dl_setattr once the update has been applied. + */ +static bool vet_deleg_attrs(struct nfsd4_setattr *setattr, struct nfs4_delegation *dp) { - struct timespec64 now =3D current_time(dp->dl_stid.sc_file->fi_inode); + struct inode *inode =3D dp->dl_stid.sc_file->fi_inode; + struct timespec64 now =3D current_time(inode); struct iattr *iattr =3D &setattr->sa_iattr; =20 - if ((setattr->sa_bmval[2] & FATTR4_WORD2_TIME_DELEG_ACCESS) && - !nfsd4_vet_deleg_time(&iattr->ia_atime, &dp->dl_atime, &now)) - iattr->ia_valid &=3D ~(ATTR_ATIME | ATTR_ATIME_SET); + /* + * The client reports the times at every DELEGRETURN, changed or not. + * Drop a report that matches the inode. An untouched file then keeps its + * change attribute, and nfsd_setattr() skips the call into the + * filesystem. Compare before clamping: a file can carry a time in the + * future, and clamping first would make the report differ from the inode + * and drag the time back to "now". + * + * The times are read without i_rwsem. A conflicting writer must break + * the delegation first, and FMODE_NOCMTIME stops the holder's own writes + * from stamping the c/mtime. touch_atime() and a CB_GETATTR can still + * move them here. A stale read then costs at most one extra update. + */ + if (setattr->sa_bmval[2] & FATTR4_WORD2_TIME_DELEG_ACCESS) { + struct timespec64 atime =3D inode_get_atime(inode); + + if (timespec64_equal(&iattr->ia_atime, &atime)) + iattr->ia_valid &=3D ~(ATTR_ATIME | ATTR_ATIME_SET); + else + clamp_deleg_time(&iattr->ia_atime, &now); + } =20 if (setattr->sa_bmval[2] & FATTR4_WORD2_TIME_DELEG_MODIFY) { - if (nfsd4_vet_deleg_time(&iattr->ia_mtime, &dp->dl_mtime, &now)) { + struct timespec64 mtime =3D inode_get_mtime(inode); + + if (dp->dl_written || + !timespec64_equal(&iattr->ia_mtime, &mtime)) { + clamp_deleg_time(&iattr->ia_mtime, &now); iattr->ia_ctime =3D iattr->ia_mtime; - if (nfsd4_vet_deleg_time(&iattr->ia_ctime, &dp->dl_ctime, &now)) - dp->dl_setattr =3D true; - else - iattr->ia_valid &=3D ~(ATTR_CTIME | ATTR_CTIME_SET); - } else { - iattr->ia_valid &=3D ~(ATTR_CTIME | ATTR_CTIME_SET | - ATTR_MTIME | ATTR_MTIME_SET); + return true; } + + iattr->ia_valid &=3D ~(ATTR_CTIME | ATTR_CTIME_SET | + ATTR_MTIME | ATTR_MTIME_SET); } + return false; } =20 static __be32 @@ -1331,7 +1368,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_co= mpound_state *cstate, .na_pacl =3D posix_acl_dup(setattr->sa_pacl), .na_dpacl =3D posix_acl_dup(setattr->sa_dpacl), }; - bool save_no_wcc, deleg_attrs; + bool save_no_wcc, deleg_attrs, deleg_cmtime =3D false; + struct nfs4_delegation *dp =3D NULL; struct nfs4_stid *st =3D NULL; struct inode *inode; __be32 status =3D nfs_ok; @@ -1356,17 +1394,15 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_= compound_state *cstate, if (deleg_attrs) { status =3D nfserr_bad_stateid; if (st && (st->sc_type & SC_TYPE_DELEG)) { - struct nfs4_delegation *dp =3D delegstateid(st); + dp =3D delegstateid(st); =20 /* Only for *_ATTRS_DELEG flavors */ if (deleg_attrs_deleg(dp->dl_type)) { - vet_deleg_attrs(setattr, dp); + deleg_cmtime =3D vet_deleg_attrs(setattr, dp); status =3D nfs_ok; } } } - if (st) - nfs4_put_stid(st); if (status) goto out_err; =20 @@ -1396,6 +1432,20 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_c= ompound_state *cstate, cstate->current_fh.fh_no_wcc =3D true; status =3D nfsd_setattr(rqstp, &cstate->current_fh, &attrs, NULL); cstate->current_fh.fh_no_wcc =3D save_no_wcc; + + /* + * The times are in place, so keep nfsd4_finalize_deleg_timestamps() from + * stamping over them. Set this only once the update has been applied: a + * failed SETATTR that set it would suppress the fallback stamp at + * DELEGRETURN and lose the timestamps of an earlier write for good. + * + * A DELEGRETURN that races this SETATTR can read dl_setattr before it is + * set and stamp "now" over the times just applied. Only a client that + * pipelines the two can hit that, and it lands on the old behavior. + */ + if (!status && deleg_cmtime) + dp->dl_setattr =3D true; + if (!status) status =3D nfserrno(attrs.na_labelerr); if (!status) @@ -1405,6 +1455,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_co= mpound_state *cstate, out: fh_drop_write(&cstate->current_fh); out_err: + if (st) + nfs4_put_stid(st); nfsd_attrs_free(&attrs); return status; } --=20 2.55.0 From nobody Sat Sep 26 04:29:50 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D4CFB4E2378; Fri, 4 Sep 2026 14:17:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531469; cv=none; b=DmHasMCqoDf5wvZv+wRdIIfOWDWblWQhje5f3PxHEnoIdTqefKSjs3jdKYaBbG97T5k7NCRDv8V58ElIX62zeR/M0dWpAcROYMurL089MNMMC2e3oVNovaNVZ72qTLXfvL1JSpHhE2GWS0CYEePIM9Z9Fb+9DW5Ux9Qeqj2WyBk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788531469; c=relaxed/simple; bh=JsdBO55uCRKWqXpn4EMIUC5/klUGfaKJEuis9Udc5ag=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=sc35jjxMKEOwJI5NHqmtYB+ZK3eZ0Sp+pclAL40+Odxe5TsOL0YpKJWnn4xNdoys6x+YDLsdZFdfsCyeFjwyesCeyNi/UQ5ItW2Nz6AReB8dUXjdZ4Z/trIwzla+d4IAzc4ya4GqtW5Cq/akH6+oKzPDJmwNh5+3rcI82kdCkZM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=K39kKo3p; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="K39kKo3p" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7605B1F00A3F; Fri, 4 Sep 2026 14:17:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788531467; bh=02WNrILEH6/OPBx3GU+DghKYQb2lTWCkSVl6B+Kwx54=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=K39kKo3pO3Wi1iIeEFYGFOIIzwtw3uSy6W2WcidhSTZVVvaRTJHxI5lnqbVRzbUIC neShbZWoVi2GqI929B7uJdIWAyYZ9TvPo2qDcIoSmYq6QwHjQwC8zj/tV7YxvCtjtB wchgUkvw7FVvIeaIM/cTr3YvcX6Rc9QghrjYt/wN0yQphH5azpXo8Qrdb880R4vcoz 4daNwQh7+572ydIMf6GtzvBWeDNhgFQIIt4MaVQdTjRM3yyiBcT5E4MsdSfcPRTjD/ 6sSZiJmH/MQ+Vl5rphFBBJXrJODaA3GeTcWuSmz6B6GG0aWp3x8YgR2NInweLep0Vj 0vZ0YIMPoppNw== From: Jeff Layton Date: Fri, 04 Sep 2026 10:17:34 -0400 Subject: [PATCH v3 3/3] nfsd: compare CB_GETATTR times against the inode Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260904-delegts-v3-3-b6062ba75f07@kernel.org> References: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> In-Reply-To: <20260904-delegts-v3-0-b6062ba75f07@kernel.org> To: Christian Brauner , Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Alexander Viro , Jan Kara Cc: Thomas Haynes , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6478; i=jlayton@kernel.org; h=from:subject:message-id; bh=JsdBO55uCRKWqXpn4EMIUC5/klUGfaKJEuis9Udc5ag=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqmtMGvDlmZOp+gTuqd0FL2nI6Pj5+/DM0BqLSQ 4i2PSc6GKSJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCaprTBgAKCRAADmhBGVaC FVPDEACRy/zpmgftM56mjgFXqVIlfI7s/4DgQx6yTqpl6iNnETQUMISV+f/IOFDmfLpl0FGQPiu TUX1WgAoYtQbk8sdRmAfGJKq8eC6zKrqikOB1phgm/tCYVcc3KXfbzyz3x0FnPDl1zawuqcs+70 QfmWxiI0oPfWTEwBv4xtsJp9/1YppsZqvfwDV3XTi/G9kKiiDJIrnoTSsi6H+op3HfF8AhN8FBA tcOe71xCHgqbTySBdmnWQar3h2fb3HaZm8Uwr6Rl9BNLRQbokXY/UJ4e4iyVbdI4BpBLB/B3gNm 2xkH9xSnR7h3oxHRR3Oh50afnav8ZXTPvc3Um6Z/Hi1FP2LxIUZFOsQNfcW+LbZeBc83Ekk5owB /0k9uLNOHu4cw4DP7ZNTcOICGiW1zmsL/B9wazjmDilZ3FlEgh078FkTY8UXJDJhMxDIVVggfYD i3Or+V8GH9jRaNiAmnWvvNpvcgA7SdMjQBstw5vdYuJnFuXFvLutqUOl6xLcb+xP0knGYOxwqEo qfz6Iv3Yqy+qO4yTnmdt9rp/FMn33g4YehgiG+VT44Ljj1z4zLUuNnf2Jjf+DPNIptZPArdrNFD z4hvbzpwHTbfa0yWrNen9hz92YILjql/ow94lyhxFWrRevLp4T7dQ0rlFwah7XTdtyjkAtKX/ln fAYvn81EknKjW5A== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 cb_getattr_update_times() vets the times that a client reports in a CB_GETATTR against dl_atime, dl_mtime and dl_ctime. nfsd samples those three when it grants the delegation, and nothing refreshes them. An earlier CB_GETATTR or SETATTR can move the inode past them. The vetting then compares against a stale value and accepts a report that it must reject. setattr_copy() applies the atime and the mtime with no check of the current value, so both move backwards. That is the result the vetting exists to prevent. Compare against the inode instead. Take the inode lock before the comparison, so that a setattr from a concurrent delegation break cannot land before notify_change() runs. With this change, there is no longer a need to keep dl_atime/mtime/ctime in the delegation. Drop those fields as well. That also drops a read of an uninitialized struct kstat: the read-delegation arm assigned stat.atime to dl_atime even when nfs4_delegation_stat() had not run. Nothing consumed the value, since dl_atime is only read under deleg_attrs_deleg(). Fixes: 3952f1cbcbc4 ("nfsd: fix SETATTR updates for delegated timestamps") Assisted-by: LLM Acked-by: Chuck Lever Signed-off-by: Jeff Layton --- fs/nfsd/nfs4state.c | 49 +++++++++++++++++++++++++++++++------------------ fs/nfsd/state.h | 8 -------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index ae0af9490fb1..b2b8d3a8030b 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -7307,9 +7307,6 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct n= fsd4_open *open, open->op_delegate_type =3D deleg_ts ? OPEN_DELEGATE_WRITE_ATTRS_DELEG : OPEN_DELEGATE_WRITE; dp->dl_cb_fattr.ncf_initial_cinfo =3D nfsd4_change_attribute(&stat); - dp->dl_atime =3D stat.atime; - dp->dl_ctime =3D stat.ctime; - dp->dl_mtime =3D stat.mtime; spin_lock(&f->f_lock); if (deleg_ts) f->f_mode |=3D FMODE_NOCMTIME; @@ -7318,7 +7315,6 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct n= fsd4_open *open, } else { open->op_delegate_type =3D deleg_ts && nfs4_delegation_stat(dp, currentf= h, &stat) ? OPEN_DELEGATE_READ_ATTRS_DELEG : OPEN_DELEGATE_READ; - dp->dl_atime =3D stat.atime; trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid); } nfs4_put_stid(&dp->dl_stid); @@ -10447,7 +10443,7 @@ nfsd4_get_writestateid(struct nfsd4_compound_state = *cstate, /** * nfsd4_vet_deleg_time - vet and set the timespec for a delegated timesta= mp update * @req: timestamp from the client - * @orig: original timestamp in the inode + * @cur: current timestamp in the inode * @now: current time * * Given a timestamp from the client response, check it against the @@ -10455,15 +10451,17 @@ nfsd4_get_writestateid(struct nfsd4_compound_stat= e *cstate, * if the inode's timestamp needs to be updated, and false otherwise. * @req may also be changed if the timestamp needs to be clamped. */ -bool nfsd4_vet_deleg_time(struct timespec64 *req, const struct timespec64 = *orig, - const struct timespec64 *now) +static bool nfsd4_vet_deleg_time(struct timespec64 *req, + const struct timespec64 *cur, + const struct timespec64 *now) { - /* - * "When the time presented is before the original time, then the - * update is ignored." Also no need to update if there is no change. + * RFC 9754 has the server ignore a time that is before the original + * one. Compare against the value in the inode rather than the original: + * an earlier CB_GETATTR or SETATTR may have moved it, and a report that + * does not advance it is stale. */ - if (timespec64_compare(req, orig) <=3D 0) + if (timespec64_compare(req, cur) <=3D 0) return false; =20 /* @@ -10484,30 +10482,45 @@ static int cb_getattr_update_times(struct dentry = *dentry, struct nfs4_delegation struct iattr attrs =3D { }; int ret; =20 + /* + * Take the inode lock first, so that a setattr from a delegation break + * cannot land between the comparison and notify_change(). The atime can + * still move under us: touch_atime() takes no lock, and FMODE_NOCMTIME + * does not cover it. + */ + inode_lock(inode); + if (deleg_attrs_deleg(dp->dl_type)) { struct timespec64 now =3D current_time(inode); + struct timespec64 atime =3D inode_get_atime(inode); + struct timespec64 mtime =3D inode_get_mtime(inode); =20 attrs.ia_atime =3D ncf->ncf_cb_atime; attrs.ia_mtime =3D ncf->ncf_cb_mtime; =20 - if (nfsd4_vet_deleg_time(&attrs.ia_atime, &dp->dl_atime, &now)) + if (nfsd4_vet_deleg_time(&attrs.ia_atime, &atime, &now)) attrs.ia_valid |=3D ATTR_ATIME | ATTR_ATIME_SET; =20 - if (nfsd4_vet_deleg_time(&attrs.ia_mtime, &dp->dl_mtime, &now)) { - attrs.ia_valid |=3D ATTR_MTIME | ATTR_MTIME_SET; + /* + * A change to the mtime must show in the ctime. + * inode_set_ctime_deleg() takes the mtime when it advances the + * ctime, and stamps the current time otherwise. + */ + if (nfsd4_vet_deleg_time(&attrs.ia_mtime, &mtime, &now)) { + attrs.ia_valid |=3D ATTR_MTIME | ATTR_MTIME_SET | + ATTR_CTIME | ATTR_CTIME_SET; attrs.ia_ctime =3D attrs.ia_mtime; - if (nfsd4_vet_deleg_time(&attrs.ia_ctime, &dp->dl_ctime, &now)) - attrs.ia_valid |=3D ATTR_CTIME | ATTR_CTIME_SET; } } else { attrs.ia_valid |=3D ATTR_MTIME | ATTR_CTIME; } =20 - if (!attrs.ia_valid) + if (!attrs.ia_valid) { + inode_unlock(inode); return 0; + } =20 attrs.ia_valid |=3D ATTR_DELEG; - inode_lock(inode); ret =3D notify_change(&nop_mnt_idmap, dentry, &attrs, NULL); inode_unlock(inode); return ret; diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index cd9294f024bb..e7fe2af45f53 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -330,11 +330,6 @@ struct nfs4_delegation { struct nfsd4_cb_notify dl_cb_notify; }; =20 - /* For delegated timestamps */ - struct timespec64 dl_atime; - struct timespec64 dl_mtime; - struct timespec64 dl_ctime; - /* For dir delegations */ u32 dl_notify_mask; u32 dl_child_attrs[2]; @@ -357,9 +352,6 @@ static inline bool deleg_attrs_deleg(u32 dl_type) dl_type =3D=3D OPEN_DELEGATE_WRITE_ATTRS_DELEG; } =20 -bool nfsd4_vet_deleg_time(struct timespec64 *cb, const struct timespec64 *= orig, - const struct timespec64 *now); - #define cb_to_delegation(cb) \ container_of(cb, struct nfs4_delegation, dl_recall) =20 --=20 2.55.0