[PATCH 1/8] ksmbd: route stream FileDispositionInformation through stream delete flag

"Gaël Blivet-Bailly" <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
From: Gael Blivet <[email protected]>

Follow-up to
commit 78380c12125a ("ksmbd: route stream FileDispositionInformation
through stream delete flag"),
which added ksmbd_fd_set_delete_pending()/ksmbd_fd_clear_delete_pending()
to keep a stream's FileDispositionInformation from marking the whole
file for deletion, but used the inode-wide S_DEL_ON_CLS_STREAM flag to
do it -- the exact same problem class the commit was fixing, one level
up.

S_DEL_ON_CLS_STREAM lives on the shared ksmbd_inode, not on any
specific stream handle. If a file has multiple stream handles open and
one gets marked delete-pending via FileDispositionInformation, the
flag can't record *which* stream should be deleted: whichever stream
handle happens to close first (not necessarily the one that was
actually marked) sees S_DEL_ON_CLS_STREAM set and has its xattr
removed. Two clients (or two handles from the same client) touching
different streams on the same file can end up deleting the wrong one.

ksmbd_inode_pending_delete() has the same issue: it only checks
S_DEL_PENDING, which is never set for a stream handle, so a client
querying FileStandardInformation.DeletePending on a stream marked via
this path would incorrectly see 0.

Track this per-handle instead (stream_del_pending on struct
ksmbd_file), matching the file itself rather than the shared inode.
ksmbd_fd_set_delete_on_close() (the CREATE-time FILE_DELETE_ON_CLOSE
option, a separate call path from FileDispositionInformation) still
uses the inode-wide flag; __ksmbd_inode_close() now checks both, since
either one should trigger removing the stream's xattr on close.

Signed-off-by: Gael Blivet <[email protected]>
---
 fs/smb/server/vfs_cache.c | 49 ++++++++++++++++++++++++++-------------
 fs/smb/server/vfs_cache.h |  7 ++++++
 2 files changed, 40 insertions(+), 16 deletions(-)

diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 2543dd7e8..1f225b16c 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -229,6 +229,16 @@ bool ksmbd_inode_pending_delete(struct ksmbd_file *fp)
 	struct ksmbd_inode *ci = fp->f_ci;
 	int ret;
 
+	/*
+	 * Stream delete-pending is tracked per-handle (see
+	 * ksmbd_fd_set_delete_pending()), not on the shared inode -- the
+	 * whole-file flags checked below would never see it set, and would
+	 * also incorrectly report a whole-file pending-delete as applying
+	 * to an unrelated stream handle on the same inode.
+	 */
+	if (ksmbd_stream_fd(fp))
+		return fp->stream_del_pending;
+
 	down_read(&ci->m_lock);
 	ret = (ci->m_flags & S_DEL_PENDING);
 	up_read(&ci->m_lock);
@@ -299,31 +309,27 @@ void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
  * mark the stream for deletion, not the whole file -- otherwise
  * deleting a single alternate data stream (e.g. AFP_AfpInfo) deletes
  * the entire file's data along with it.
+ *
+ * This is tracked on fp itself (stream_del_pending), not the shared
+ * ksmbd_inode: the inode-wide S_DEL_ON_CLS_STREAM flag used by
+ * ksmbd_fd_set_delete_on_close() can't record *which* stream should be
+ * deleted, so if a different stream handle on the same file closed
+ * first, it would delete the wrong stream.
  */
 void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp)
 {
-	struct ksmbd_inode *ci = fp->f_ci;
-
-	if (ksmbd_stream_fd(fp)) {
-		down_write(&ci->m_lock);
-		ci->m_flags |= S_DEL_ON_CLS_STREAM;
-		up_write(&ci->m_lock);
-	} else {
+	if (ksmbd_stream_fd(fp))
+		fp->stream_del_pending = true;
+	else
 		ksmbd_set_inode_pending_delete(fp);
-	}
 }
 
 void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp)
 {
-	struct ksmbd_inode *ci = fp->f_ci;
-
-	if (ksmbd_stream_fd(fp)) {
-		down_write(&ci->m_lock);
-		ci->m_flags &= ~S_DEL_ON_CLS_STREAM;
-		up_write(&ci->m_lock);
-	} else {
+	if (ksmbd_stream_fd(fp))
+		fp->stream_del_pending = false;
+	else
 		ksmbd_clear_inode_pending_delete(fp);
-	}
 }
 
 static void ksmbd_inode_hash(struct ksmbd_inode *ci)
@@ -446,6 +452,17 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp)
 		}
 		up_write(&ci->m_lock);
 
+		/*
+		 * Per-handle delete-pending from ksmbd_fd_set_delete_pending()
+		 * (FileDispositionInformation on this stream) -- separate from
+		 * the inode-wide flag above, which only ever meant "some
+		 * stream on this file" with no way to say which one.
+		 */
+		if (fp->stream_del_pending) {
+			fp->stream_del_pending = false;
+			remove_stream_xattr = true;
+		}
+
 		if (remove_stream_xattr) {
 			const struct cred *saved_cred;
 
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 111a4e315..f796b6edc 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -118,6 +118,13 @@ struct ksmbd_file {
 	struct list_head		node;
 	struct list_head		blocked_works;
 	struct list_head		lock_list;
+	/*
+	 * Per-handle FileDispositionInformation delete-pending state for a
+	 * stream handle -- separate from ksmbd_inode's inode-wide m_flags,
+	 * which have no way to record which stream on a multi-stream file
+	 * was actually marked for deletion. See ksmbd_fd_set_delete_pending().
+	 */
+	bool				stream_del_pending;
 
 	unsigned int			durable_timeout;
 	unsigned int			durable_scavenger_timeout;

base-commit: 545c40084bb831530033265b654290ff7be5b090
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.