From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793898867; b=oXQN4Myb8uP8zOr3wcXt4jTtYL8USevbTBUJzEmKZRUCnbLAnsbG8Kn8XWZxofFLVEg8 PQOFQCVXo1xzP/sDGR9sH7GNpsHzpza1QAowLngikCQsG024H9GfVY/rTei7DCzMevb6k FWgO1dBh2Q043YbtdGLJ8JQCgCQjNoYXo6Uunx8edODg7b6X2L2Ty+vze+U+aRkkh04JO 5+/OWepVBnoGE/6Tcb6lxygoediznCLtJGGgoip/IUwhGOcqu/8a0sE6YGpLmnEABBE+L GVtg6wFrW0N1B21lBjU/W62NMni/uzMNTPIdiDMeRF34N898ajsg1RB3b/ECPBWb7cw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793898867; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=n1AufrOCp0p2worQjoAwlC2oVsv8uvDcWVoy7h5E3fY=; b=piqSWmU38cJLZ5X7vk0OuL9DV91y8kxu+LGehoTzJe6Koq7rEDJqMIR2XJKzxLB9sXbi JUs2f/IglNJRSI4X+ix5Oczp2Sxi8TyYKoGwgstqZGkOW+n2pCJTBffMETQMU/sLaZ/wH 8P5nUce4vUTPJTft4RWwxbg5l26eubccp66tiMTxi8n4mrNWJBV132kO/3tafDanMrSe4 vDIYIvoKrIrCKA8io7g9d2gv0zx92cTpFpOnwGgY1VQEZ/FwLf+jdvbUMrs4g3RQ2uRM4 /XcA7jTTQNevORz9VOi4fZNBE5yWwlNVxjZ6sJt2sXiAxnXOOheIjFhEsiJho5lI5Sg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=UuO53RA3; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A883F606A31B for ; Thu, 5 Nov 2026 17:14:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=UuO53RA3 Received: by smtp.kernel.org (Postfix) with ESMTPSA id F35181F000C1; Thu, 5 Nov 2026 17:14:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1793898866; bh=n1AufrOCp0p2worQjoAwlC2oVsv8uvDcWVoy7h5E3fY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=UuO53RA3aLqW+9X+y96b0GEy6REBYuUF1PLjbWGzGH24sCZwHXPoW8Ar1ODh5dtSN yaubH4VOnPgLNe5QnPxFApVZTuMKyW9PQmuoAQiBZtSOqOYiirPejK9g+/Bkg15QE8 sLguGBRc7lmb/qP4MDTg/GmWlkMI2t6dB99aKlrrXgOelZmjuYO6VEp96tja+1BzBv VThKkvieeaG+3FmkiB5ap4qSgqoj6H3pDNHMoaCFDnk1b1Ht14wTRWFDcmikYWtMeP N87dqyP7NjcwZ8gpjNC3oTsDtn/WFUIlGfsnG213PLUAwTj6hZF84J6DyTsTfdGi7N ZW0NcEHcLtIXg== From: Sasha Levin To: stable@vger.kernel.org Cc: Weiming Shi , co+d531129f2f9fbf88@bugs.sh, Namjae Jeon , Sasha Levin Subject: [PATCH 7.3.y] smb/server: prevent file publication after tree disconnect Date: Thu, 5 Nov 2026 12:14:24 -0500 Message-ID: <20261105171318.2152308-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <2026110449-crib-grimy-afb3@gregkh> References: <2026110449-crib-grimy-afb3@gregkh> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Weiming Shi [ Upstream commit 2ff3730998b015b5b0a3f28330bee16c648dd114 ] TREE_DISCONNECT closes the tree's published file handles before marking and removing the tree connection. A concurrent CREATE that already holds a tree reference can therefore publish its file after the close pass. When CREATE drops its reference, the tree can be freed while the published file still points to it. Mark and remove the tree connection before closing its files, and serialize both new-open and durable-reconnect volatile ID publication with that state transition. An opener is then either published in time for the close pass or rejected after disconnect with STATUS_NETWORK_NAME_DELETED. Keep a tree reference while a file carries its tree pointer. This protects asynchronous users that can defer the file's final destruction past tree disconnect. The close pass can race the successful FP_NEW to FP_INITED transition. Take the opening request's file reference atomically with that transition. This keeps the file alive until request completion even if teardown removes the ID immediately. BUG: KASAN: slab-use-after-free in proc_show_files (fs/smb/server/vfs_cache.c:126) Read of size 4 at addr ff11000101540f80 by task poc/153 Call Trace: proc_show_files (fs/smb/server/vfs_cache.c:126) seq_read_iter (fs/seq_file.c:231) proc_reg_read_iter (fs/proc/inode.c:299) vfs_read (fs/read_write.c:574) ksys_read (fs/read_write.c:716) do_syscall_64 (arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Allocated by: ksmbd_tree_conn_connect (fs/smb/server/mgmt/tree_connect.c:35) smb2_tree_connect (fs/smb/server/smb2pdu.c:2819) handle_ksmbd_work (fs/smb/server/server.c:368) Freed by: kfree (mm/slub.c:6801) handle_ksmbd_work (fs/smb/server/server.c:332) Fixes: f44158485826 ("cifsd: add file operations") Cc: stable@vger.kernel.org Reported-by: Assisted-by: LLM Signed-off-by: Weiming Shi Signed-off-by: Namjae Jeon [ sashal: replaced ksmbd_tree_connect_get() calls with equivalent inline atomic_inc() operations ] Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 27 ++++++++++++++---- fs/smb/server/vfs_cache.c | 58 ++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 3f1ab9a203070..418610e05ce95 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3097,9 +3097,10 @@ int smb2_tree_disconnect(struct ksmbd_work *work) goto err_out; } + err = ksmbd_tree_conn_disconnect(sess, tcon); + ksmbd_close_tree_conn_fds(work); - err = ksmbd_tree_conn_disconnect(sess, tcon); if (err) { rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; goto err_out; @@ -4439,8 +4440,12 @@ int smb2_open(struct ksmbd_work *work) goto err_out2; rc = ksmbd_reopen_durable_fd(work, dh_info.fp); - if (rc) + if (rc) { + if (rc == -ENOTCONN) + rsp->hdr.Status = + STATUS_NETWORK_NAME_DELETED; goto err_out2; + } fp = dh_info.fp; @@ -4874,6 +4879,8 @@ int smb2_open(struct ksmbd_work *work) if (IS_ERR(fp)) { fput(filp); rc = PTR_ERR(fp); + if (rc == -ENOTCONN) + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; fp = NULL; goto err_out; } @@ -5477,11 +5484,20 @@ int smb2_open(struct ksmbd_work *work) err_out2: if (!rc) { - if (!dh_info.replay) + if (!dh_info.replay) { + smb2_complete_request_open(work); rc = ksmbd_update_fstate(&work->sess->file_table, fp, FP_INITED); - if (!rc) + if (rc == -ENOENT) + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; + if (!rc) { + work->request_open = fp; + work->request_open_chseq = + smb3_hdr_channel_sequence(&req->hdr); + } + } else { rc = smb2_set_request_open(work, fp, &req->hdr, false, false); + } if (!rc) rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len); } @@ -5496,8 +5512,7 @@ int smb2_open(struct ksmbd_work *work) rsp->hdr.Status = STATUS_PRIVILEGE_NOT_HELD; else rsp->hdr.Status = STATUS_ACCESS_DENIED; - } - else if (rc == -ENOENT) + } else if (rc == -ENOENT && !rsp->hdr.Status) rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID; else if (rc == -EPERM) rsp->hdr.Status = STATUS_SHARING_VIOLATION; diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 1efa9f8145601..38196befa03a5 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -698,16 +698,18 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp) } /* - * Drop fp's strong reference on conn (taken in ksmbd_open_fd() / - * ksmbd_reopen_durable_fd()). Durable fps that reached the - * scavenger have already had fp->conn cleared by session_fd_check(), - * in which case there is nothing to drop here. + * Drop fp's strong connection references. Durable fps that reached + * the scavenger have already had these cleared by session_fd_check(). */ if (fp->conn) { atomic_dec(&fp->conn->stats.open_files_count); ksmbd_conn_put(fp->conn); fp->conn = NULL; } + if (fp->tcon) { + ksmbd_tree_connect_put(fp->tcon); + fp->tcon = NULL; + } if (ksmbd_stream_fd(fp)) kfree(fp->stream.name); @@ -1331,14 +1333,14 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp) fp->filp = filp; /* - * fp owns a strong reference on fp->conn for as long as fp->conn is - * non-NULL, so session_fd_check() and __ksmbd_close_fd() never - * dereference a dangling pointer. Paired with ksmbd_conn_put() in - * session_fd_check() (durable preserve), in __ksmbd_close_fd() - * (final close), and on the error paths below. + * fp owns strong references on fp->conn and fp->tcon for as long as + * they are non-NULL. Paired with the puts in session_fd_check() + * (durable preserve), in __ksmbd_close_fd() (final close), and on the + * error paths below. */ fp->conn = ksmbd_conn_get(work->conn); fp->tcon = work->tcon; + atomic_inc(&fp->tcon->refcount); fp->share_conf = work->tcon->share_conf; atomic_inc(&fp->share_conf->refcount); fp->volatile_id = KSMBD_NO_FID; @@ -1351,7 +1353,13 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp) goto err_out; } - ret = __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID); + down_read(&work->sess->tree_conns_lock); + if (work->tcon->t_state == TREE_CONNECTED) + ret = __open_id(&work->sess->file_table, fp, + OPEN_ID_TYPE_VOLATILE_ID); + else + ret = -ENOTCONN; + up_read(&work->sess->tree_conns_lock); if (ret) { ksmbd_inode_put(fp->f_ci); goto err_out; @@ -1361,7 +1369,8 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp) return fp; err_out: - /* fp->conn was set and refcounted before every branch here. */ + /* fp->conn and fp->tcon were refcounted before every branch here. */ + ksmbd_tree_connect_put(fp->tcon); ksmbd_conn_put(fp->conn); ksmbd_share_config_put(fp->share_conf); kmem_cache_free(filp_cache, fp); @@ -1376,8 +1385,9 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp) * * Return: 0 on success. The FP_NEW -> FP_INITED transition is special: * -ENOENT if teardown already unpublished @fp by advancing the state or - * clearing the volatile id. Other state updates preserve the historical - * fire-and-forget behavior. + * clearing the volatile id. On success it also takes a reference for the + * opening request, which must be released with ksmbd_fd_put(). Other state + * updates preserve the historical fire-and-forget behavior. */ int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, unsigned int state) @@ -1393,6 +1403,8 @@ int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, ret = -ENOENT; } else { fp->f_state = state; + if (state == FP_INITED) + atomic_inc(&fp->refcount); ret = 0; } write_unlock(&ft->lock); @@ -1813,6 +1825,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, struct oplock_info *op; struct ksmbd_conn *conn; struct ksmbd_lock *smb_lock, *tmp_lock; + struct ksmbd_tree_connect *tree_conn; if (!is_reconnectable(fp)) return false; @@ -1834,6 +1847,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, ci = fp->f_ci; down_write(&ci->m_lock); conn = fp->conn; + tree_conn = fp->tcon; list_for_each_entry_rcu(op, &ci->m_op_list, op_entry, lockdep_is_held(&ci->m_lock)) { if (op->o_fp != fp) @@ -1869,6 +1883,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, /* Drop fp's own reference on conn. */ ksmbd_conn_put(conn); + ksmbd_tree_connect_put(tree_conn); return true; } @@ -1972,7 +1987,8 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) * concurrent teardown that iterates the table can observe a valid * volatile_id with fp->conn == NULL and preserve a * partially-initialized fp. fp owns a strong reference on the new - * conn (see ksmbd_open_fd()); undo it on __open_id() failure. + * conn (see ksmbd_open_fd()); undo it on __open_id() failure. Serialize + * publication with tree disconnect just as ksmbd_open_fd() does. */ spin_lock(&fp->f_lock); old_f_state = fp->f_state; @@ -1982,12 +1998,19 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) atomic_inc(&fp->share_conf->refcount); fp->conn = ksmbd_conn_get(conn); fp->tcon = work->tcon; + atomic_inc(&fp->tcon->refcount); spin_unlock(&fp->f_lock); atomic_inc(&conn->stats.open_files_count); write_unlock(&global_ft.lock); - __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID); - if (!has_file_id(fp->volatile_id)) { + down_read(&work->sess->tree_conns_lock); + if (work->tcon->t_state == TREE_CONNECTED) + ret = __open_id(&work->sess->file_table, fp, + OPEN_ID_TYPE_VOLATILE_ID); + else + ret = -ENOTCONN; + up_read(&work->sess->tree_conns_lock); + if (ret) { write_lock(&global_ft.lock); spin_lock(&fp->f_lock); fp->conn = NULL; @@ -1998,7 +2021,10 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) atomic_dec(&conn->stats.open_files_count); write_unlock(&global_ft.lock); ksmbd_conn_put(conn); + ksmbd_tree_connect_put(work->tcon); ksmbd_share_config_put(work->tcon->share_conf); + if (ret != -ENOTCONN) + ret = -EBADF; goto out; } -- 2.53.0