ksmbd: supersede disconnected delete-on-close durable handle

A durable handle opened with FILE_DELETE_ON_CLOSE is preserved across a
disconnect so it can be reclaimed by a durable reconnect.
smb2.durable-open.delete_on_close2 disconnects such a handle and then
reconnects it, expecting the reconnect to succeed.

When the client does not reconnect but instead opens the same name with a
new delete-on-close create, the preserved handle keeps the file present
with delete-on-close set. ksmbd then rejects the new open with
STATUS_ACCESS_DENIED on the file_present + FILE_DELETE_ON_CLOSE +
OPEN_IF/OVERWRITE_IF path. smb2.durable-open.delete_on_close1 expects this
open to create a fresh, empty file instead, i.e. the disconnected handle's
delete-on-close must take effect first.

Add ksmbd_close_disconnected_durable_delete_on_close(), which closes
disconnected (conn == NULL) durable handles that keep a delete-on-close
file present. The final close promotes S_DEL_ON_CLS to S_DEL_PENDING and
unlinks the file, so a re-resolved path is absent and the new open creates
it fresh. Call it from smb2_open() before the delete-on-close conflict
check, only for the conflicting open shapes. A live (connected) handle
still keeps the file and blocks the open as before.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Namjae Jeon 2026-06-21 19:33:03 +09:00 committed by Steve French
parent a04159d96c
commit 166e4c0702
3 changed files with 75 additions and 0 deletions

View File

@ -3272,6 +3272,23 @@ int smb2_open(struct ksmbd_work *work)
rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
&path, 1);
/*
* A durable handle opened with delete-on-close is preserved across a
* disconnect so it can be reclaimed by a durable reconnect. When a new
* delete-on-close open for the same name arrives instead, the
* disconnected handle must give way: close it so its delete-on-close
* removes the file, then re-resolve so this open can create a fresh one.
*/
if (!rc && (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) &&
(req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
req->CreateDisposition == FILE_OPEN_IF_LE) &&
ksmbd_close_disconnected_durable_delete_on_close(path.dentry)) {
path_put(&path);
rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
&path, 1);
}
if (!rc) {
file_present = true;

View File

@ -517,6 +517,63 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
kmem_cache_free(filp_cache, fp);
}
/**
* ksmbd_close_disconnected_durable_delete_on_close() - drop a delete-on-close
* file kept present only by disconnected durable handles
* @dentry: dentry of the file being opened
*
* A durable handle opened with delete-on-close is preserved across a
* disconnect so it can be reclaimed by a durable reconnect. When a new
* (non-reconnect) open arrives for the same name instead, the disconnected
* handle has to give way. Close such handles so their delete-on-close is
* applied and the file is removed once the last handle is gone, letting the
* new open create a fresh file.
*
* The caller's inode reference is dropped before closing so that the final
* close can promote S_DEL_ON_CLS to S_DEL_PENDING and unlink the file.
*
* Return: true if a disconnected durable handle was closed.
*/
bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
{
struct ksmbd_inode *ci;
struct ksmbd_file *fp, *tmp;
LIST_HEAD(dispose);
bool closed = false;
ci = ksmbd_inode_lookup_lock(dentry);
if (!ci)
return false;
down_write(&ci->m_lock);
if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_ON_CLS_STREAM | S_DEL_PENDING)) {
list_for_each_entry_safe(fp, tmp, &ci->m_fp_list, node) {
if (fp->conn || !fp->is_durable ||
fp->f_state != FP_INITED)
continue;
list_move_tail(&fp->node, &dispose);
}
}
up_write(&ci->m_lock);
/*
* Drop our lookup reference before closing so the last __ksmbd_close_fd()
* can drop m_count to zero and unlink the delete-on-close file. The
* collected handles still hold references, so ci stays valid until they
* are closed below.
*/
ksmbd_inode_put(ci);
while (!list_empty(&dispose)) {
fp = list_first_entry(&dispose, struct ksmbd_file, node);
list_del_init(&fp->node);
__ksmbd_close_fd(NULL, fp);
closed = true;
}
return closed;
}
static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
{
if (fp->f_state != FP_INITED)

View File

@ -160,6 +160,7 @@ struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);
bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
void ksmbd_put_durable_fd(struct ksmbd_file *fp);