50 ksmbd server fixes

-----BEGIN PGP SIGNATURE-----
 
 iQGzBAABCgAdFiEE6fsu8pdIjtWE/DpLiiy9cAdyT1EFAmo9nx0ACgkQiiy9cAdy
 T1GiNgwAuxlhhJghBrXqNqzW7FwJRsrC+ibEfCQDdI5ifREo+6Ov6I9FBEu5z/XX
 OAXFJWRHYHzZEc7XqpjXOqIXRq9GN3ADA2bwQ0fsh94TazmIeYXEDcMDnutQOECM
 rqm+5kO+mY+c76XPm1C1CcNi+Te3q7Ry4O6rY7JhgGn5agZPyEEVHQ2nhRbS4YCp
 LQESWWd169CIeHkBJCUnPbLPPaC56pkkHaEKgoPuZk0lbSTMOeQrmWInxX2lXBNQ
 udO79m7Fz2XrGy0vjBwBFfOaWdBd2Gk1b76Hc/3EwfVVm2NN8w6ge0L4m+1RglDz
 3VQSBsJ3nNVNRxSLpfjRwAE+pFvIy7+XmHA4lJ0REeklpyVF+27i/QvqPuCovm/g
 AfMbrExIcDt+96DEe+h5iJtbP7WiuCIsYWtvpUtnDUdz4aTrmRlJhzaDvz88vmDi
 5GRmLm13oGoPN3UTF3PQDgFG4Jigs+1YlKrbeIBOXkIbcYBslIDIIxN0b82i2GRT
 6osoVCRX
 =/FfE
 -----END PGP SIGNATURE-----

Merge tag 'v7.2-rc-part2-smb3-server-fixes' of git://git.samba.org/ksmbd

Pull smb server updates from Steve French:
 "This is mostly a correctness and compatibility update for ksmbd's
  SMB2/3 lease, oplock, durable handle, compound request, CREATE,
  rename, stream and share-mode handling.

  A large part of the series fixes cases found by smbtorture where ksmbd
  diverged from the SMB2/3 protocol requirements.

  The main changes are:

   - Rework SMB2 lease state handling so lease state is shared per
     ClientGuid/LeaseKey across opens, with better validation of lease
     create contexts, ACK handling, epochs, break-in-progress reporting,
     v2 lease notification routing, and chained lease breaks

   - Fix several oplock break corner cases, including ACK validation,
     timeout downgrade behavior, level-II break handling on unlink,
     share-conflict lease breaks, and read-control/stat-open behavior

   - Fix durable handle behavior around delete-on-close, stale
     reconnects, reconnect context parsing, oplock/lease break
     invalidation, and durable v2 AppInstanceId replacement

   - Fix compound request handling so related commands propagate failed
     statuses correctly, preserve response framing across chained
     errors, keep compound FIDs across READ/WRITE/FLUSH, and send
     interim STATUS_PENDING where clients expect cancellable compound
     I/O

   - Tighten CREATE and stream semantics, including create attribute
     validation, allocation size reporting, explicit create security
     descriptors, unnamed DATA stream handling, stream directory
     validation, and stream delete sharing against the base file

   - Fix rename and metadata behavior, including parent directory
     sharing checks, denying directory rename with open children, and
     preserving SMB ChangeTime across rename for open handles

   - Fix two important safety issues: a multichannel byte-range lock
     list owner race that could lead to use-after-free, and an NTLMv2
     session key update before authentication proof validation

   - Fix a concurrent SMB2 NEGOTIATE preauth use-after-free, a UBSAN
     warning in compression capability parsing, a false hung-task
     warning in the durable handle scavenger, endian debug logging,
     Smatch indentation warnings, and kernel-doc warnings

   - Increase the default SMB3 transaction size from 1MB to 4MB to
     better match modern read/write negotiation and improve sequential
     I/O behavior"

* tag 'v7.2-rc-part2-smb3-server-fixes' of git://git.samba.org/ksmbd: (50 commits)
  ksmbd: fix kernel-doc warnings in smb2_lease_break_noti()
  ksmbd: fix inconsistent indenting warnings
  ksmbd: validate NTLMv2 response before updating session key
  ksmbd: increase SMB3_DEFAULT_TRANS_SIZE from 1MB to 4MB
  ksmbd: fix UBSAN array-index-out-of-bounds in decode_compress_ctxt()
  ksmbd: sleep interruptibly in the durable handle scavenger
  ksmbd: start file id allocation at 1
  ksmbd: treat read-control opens as stat opens only for leases
  ksmbd: validate :: stream type against directory create
  ksmbd: break conflicting-open leases only as far as needed
  ksmbd: break handle caching for share conflicts
  ksmbd: normalize ungrantable lease states
  ksmbd: return oplock protocol error for level II ack
  ksmbd: avoid level II oplock break notification on unlink
  ksmbd: downgrade oplock after break timeout
  ksmbd: apply create security descriptor first
  ksmbd: return requested create allocation size
  ksmbd: tighten create file attribute validation
  ksmbd: reject empty-attribute synchronize-only create
  ksmbd: honor stream delete sharing for base file
  ...
This commit is contained in:
Linus Torvalds 2026-06-26 08:17:42 -07:00
commit 71fab6fa76
11 changed files with 1319 additions and 447 deletions

View File

@ -142,6 +142,7 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
{
char ntlmv2_hash[CIFS_ENCPWD_SIZE];
char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
char sess_key[SMB2_NTLMV2_SESSKEY_SIZE];
struct hmac_md5_ctx ctx;
int rc;
@ -164,12 +165,21 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
/* Generate the session key */
hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE,
ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE,
sess->sess_key);
sess_key);
if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
CIFS_HMAC_MD5_HASH_SIZE))
return -EINVAL;
return 0;
CIFS_HMAC_MD5_HASH_SIZE)) {
rc = -EINVAL;
goto out;
}
memcpy(sess->sess_key, sess_key, sizeof(sess_key));
rc = 0;
out:
memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash));
memzero_explicit(ntlmv2_rsp, sizeof(ntlmv2_rsp));
memzero_explicit(sess_key, sizeof(sess_key));
return rc;
}
/**
@ -226,6 +236,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
nt_len - CIFS_ENCPWD_SIZE,
domain_name, conn->ntlmssp.cryptkey);
kfree(domain_name);
if (ret)
return ret;
/* The recovered secondary session key */
if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) {

View File

@ -16,6 +16,36 @@
static struct kmem_cache *work_cache;
static struct workqueue_struct *ksmbd_wq;
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
struct kvec *new;
int new_alloc_cnt = work->iov_alloc_cnt;
if (work->iov_alloc_cnt >= work->iov_cnt + need_iov_cnt)
return 0;
do {
new_alloc_cnt += KSMBD_WORK_INLINE_IOVS;
} while (new_alloc_cnt < work->iov_cnt + need_iov_cnt);
if (work->iov == work->iov_inline) {
new = kcalloc(new_alloc_cnt, sizeof(*new), KSMBD_DEFAULT_GFP);
if (!new)
return -ENOMEM;
memcpy(new, work->iov_inline, sizeof(work->iov_inline));
} else {
new = krealloc(work->iov, sizeof(*new) * new_alloc_cnt,
KSMBD_DEFAULT_GFP | __GFP_ZERO);
if (!new)
return -ENOMEM;
}
work->iov = new;
work->iov_alloc_cnt = new_alloc_cnt;
return 0;
}
struct ksmbd_work *ksmbd_alloc_work_struct(void)
{
struct ksmbd_work *work = kmem_cache_zalloc(work_cache, KSMBD_DEFAULT_GFP);
@ -27,13 +57,8 @@ struct ksmbd_work *ksmbd_alloc_work_struct(void)
INIT_LIST_HEAD(&work->async_request_entry);
INIT_LIST_HEAD(&work->fp_entry);
INIT_LIST_HEAD(&work->aux_read_list);
work->iov_alloc_cnt = 4;
work->iov = kzalloc_objs(struct kvec, work->iov_alloc_cnt,
KSMBD_DEFAULT_GFP);
if (!work->iov) {
kmem_cache_free(work_cache, work);
work = NULL;
}
work->iov_alloc_cnt = ARRAY_SIZE(work->iov_inline);
work->iov = work->iov_inline;
}
return work;
}
@ -55,7 +80,8 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
kfree(work->tr_buf);
kvfree(work->compress_buf);
kvfree(work->request_buf);
kfree(work->iov);
if (work->iov != work->iov_inline)
kfree(work->iov);
if (work->async_id)
ksmbd_release_id(&work->conn->async_ida, work->async_id);
@ -117,19 +143,9 @@ static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
return -ENOMEM;
}
if (work->iov_alloc_cnt < work->iov_cnt + need_iov_cnt) {
struct kvec *new;
work->iov_alloc_cnt += 4;
new = krealloc(work->iov,
sizeof(struct kvec) * work->iov_alloc_cnt,
KSMBD_DEFAULT_GFP | __GFP_ZERO);
if (!new) {
kfree(ar);
work->iov_alloc_cnt -= 4;
return -ENOMEM;
}
work->iov = new;
if (ksmbd_reserve_iov(work, need_iov_cnt)) {
kfree(ar);
return -ENOMEM;
}
/* Plus rfc_length size on first iov */

View File

@ -13,6 +13,8 @@ struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
#define KSMBD_WORK_INLINE_IOVS 4
enum {
KSMBD_WORK_ACTIVE = 0,
KSMBD_WORK_CANCELLED,
@ -42,6 +44,7 @@ struct ksmbd_work {
int iov_alloc_cnt;
int iov_cnt;
int iov_idx;
struct kvec iov_inline[KSMBD_WORK_INLINE_IOVS];
/* Next cmd hdr in compound req buf*/
int next_smb2_rcv_hdr_off;
@ -57,6 +60,7 @@ struct ksmbd_work {
u64 compound_fid;
u64 compound_pfid;
u64 compound_sid;
__le32 compound_status;
const struct cred *saved_cred;

View File

@ -121,7 +121,9 @@ int parse_stream_name(char *filename, char **stream_name, int *s_type)
char *stream_type;
char *s_name;
int rc = 0;
bool has_stream_type = false;
*stream_name = NULL;
s_name = filename;
filename = strsep(&s_name, ":");
ksmbd_debug(SMB, "filename : %s, streams : %s\n", filename, s_name);
@ -137,14 +139,20 @@ int parse_stream_name(char *filename, char **stream_name, int *s_type)
ksmbd_debug(SMB, "stream name : %s, stream type : %s\n", s_name,
stream_type);
if (!strncasecmp("$data", stream_type, 5))
if (!strncasecmp("$data", stream_type, 5)) {
*s_type = DATA_STREAM;
else if (!strncasecmp("$index_allocation", stream_type, 17))
has_stream_type = true;
} else if (!strncasecmp("$index_allocation", stream_type, 17)) {
*s_type = DIR_STREAM;
else
has_stream_type = true;
} else {
rc = -ENOENT;
}
}
if (has_stream_type && !s_name[0] && *s_type == DATA_STREAM)
goto out;
*stream_name = s_name;
out:
return rc;

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ struct lease_ctx_info {
struct lease_table {
char client_guid[SMB2_CLIENT_GUID_SIZE];
struct ksmbd_conn *conn;
struct list_head lease_list;
struct list_head l_entry;
spinlock_t lb_lock;
@ -49,7 +50,13 @@ struct lease {
int version;
unsigned short epoch;
bool is_dir;
bool reuse_epoch;
struct ksmbd_inode *ci;
struct lease_table *l_lb;
struct list_head l_entry;
struct list_head open_list;
spinlock_t lock;
atomic_t refcount;
};
struct oplock_info {
@ -92,6 +99,10 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
struct lease_ctx_info *lctx, int share_ret);
void smb_break_all_levII_oplock(struct ksmbd_work *work,
struct ksmbd_file *fp, int is_trunc);
void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
struct ksmbd_file *fp, int is_trunc);
void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
struct ksmbd_file *fp);
int opinfo_write_to_read(struct oplock_info *opinfo);
int opinfo_read_handle_to_read(struct oplock_info *opinfo);
int opinfo_write_to_none(struct oplock_info *opinfo);
@ -105,6 +116,7 @@ void opinfo_put(struct oplock_info *opinfo);
void create_lease_buf(u8 *rbuf, struct lease *lease);
struct lease_ctx_info *parse_lease_state(void *open_req);
__u8 smb2_map_lease_to_oplock(__le32 lease_state);
void lease_update_oplock_levels(struct lease *lease);
int lease_read_to_write(struct oplock_info *opinfo);
/* Durable related functions */
@ -116,7 +128,7 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp);
struct create_context *smb2_find_context_vals(void *open_req, const char *tag, int tag_len);
struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
char *lease_key);
int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
struct lease_ctx_info *lctx);
void destroy_lease_table(struct ksmbd_conn *conn);
void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@
#define MAX_SMB2_HDR_SIZE 0x78 /* 4 len + 64 hdr + (2*24 wct) + 2 bct + 2 pad */
#define SMB21_DEFAULT_IOSIZE (1024 * 1024)
#define SMB3_DEFAULT_TRANS_SIZE (1024 * 1024)
#define SMB3_DEFAULT_TRANS_SIZE (4 * 1024 * 1024)
#define SMB3_MIN_IOSIZE (64 * 1024)
#define SMB3_MAX_IOSIZE (8 * 1024 * 1024)
#define SMB3_MAX_MSGSIZE (4 * 4096)

View File

@ -700,10 +700,18 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
if (err)
goto out_drop_write;
if (!work->tcon->posix_extensions && d_is_dir(old_child) &&
ksmbd_has_open_files(old_child)) {
err = -EACCES;
goto out3;
}
parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
if (parent_fp) {
if (parent_fp->daccess & FILE_DELETE_LE) {
pr_err("parent dir is opened with delete access\n");
if ((parent_fp->daccess & FILE_DELETE_LE) ||
(!parent_fp->attrib_only &&
!(parent_fp->saccess & FILE_SHARE_DELETE_LE))) {
pr_err("parent dir blocks delete sharing\n");
err = -ESHARE;
ksmbd_fd_put(work, parent_fp);
goto out3;

View File

@ -10,6 +10,7 @@
#include <linux/vmalloc.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/dcache.h>
#include "glob.h"
#include "vfs_cache.h"
@ -37,6 +38,8 @@ static struct ksmbd_file_table global_ft;
static atomic_long_t fd_limit;
static struct kmem_cache *filp_cache;
static int ksmbd_mark_fp_closed(struct ksmbd_file *fp);
#define OPLOCK_NONE 0
#define OPLOCK_EXCLUSIVE 1
#define OPLOCK_BATCH 2
@ -251,6 +254,33 @@ void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp)
up_write(&ci->m_lock);
}
bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp)
{
struct ksmbd_file *prev_fp;
struct ksmbd_inode *ci = fp->f_ci;
bool ret = false;
if (ksmbd_stream_fd(fp))
return false;
down_read(&ci->m_lock);
list_for_each_entry(prev_fp, &ci->m_fp_list, node) {
if (prev_fp == fp || !ksmbd_stream_fd(prev_fp))
continue;
if (file_inode(fp->filp) != file_inode(prev_fp->filp))
continue;
if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE)) {
ret = true;
break;
}
}
up_read(&ci->m_lock);
return ret;
}
void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
int file_info)
{
@ -484,10 +514,14 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
* there are not accesses to fp->lock_list.
*/
list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
if (!list_empty(&smb_lock->clist) && fp->conn) {
spin_lock(&fp->conn->llist_lock);
list_del(&smb_lock->clist);
spin_unlock(&fp->conn->llist_lock);
struct ksmbd_conn *conn = smb_lock->conn;
if (conn) {
spin_lock(&conn->llist_lock);
list_del_init(&smb_lock->clist);
smb_lock->conn = NULL;
spin_unlock(&conn->llist_lock);
ksmbd_conn_put(conn);
}
list_del(&smb_lock->flist);
@ -513,6 +547,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)
@ -576,6 +667,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
{
struct ksmbd_file *fp;
struct ksmbd_file_table *ft;
bool closed = false;
if (!has_file_id(id))
return 0;
@ -590,6 +682,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
fp = NULL;
else {
fp->f_state = FP_CLOSED;
closed = true;
if (!atomic_dec_and_test(&fp->refcount))
fp = NULL;
}
@ -597,7 +690,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
write_unlock(&ft->lock);
if (!fp)
return -EINVAL;
return closed ? 0 : -EINVAL;
__put_fd_final(work, fp);
return 0;
@ -670,7 +763,8 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id)
struct ksmbd_file *fp;
fp = __ksmbd_lookup_fd(&global_ft, id);
if (fp && (fp->conn ||
if (fp && (fp->durable_reconnect_disabled ||
fp->conn ||
(fp->durable_scavenger_timeout &&
(fp->durable_scavenger_timeout <
jiffies_to_msecs(jiffies))))) {
@ -689,6 +783,122 @@ void ksmbd_put_durable_fd(struct ksmbd_file *fp)
__ksmbd_close_fd(NULL, fp);
}
bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
{
struct ksmbd_file *lfp;
struct ksmbd_inode *ci = fp->f_ci;
bool ret = false;
down_read(&ci->m_lock);
list_for_each_entry(lfp, &ci->m_fp_list, node) {
if (lfp == fp)
continue;
if (lfp->f_state == FP_INITED &&
(READ_ONCE(lfp->conn) || READ_ONCE(lfp->tcon))) {
ret = true;
break;
}
}
up_read(&ci->m_lock);
return ret;
}
static struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
{
struct ksmbd_file *fp = NULL;
unsigned int id;
if (!memchr_inv(app_instance_id, 0, SMB2_CREATE_GUID_SIZE))
return NULL;
read_lock(&global_ft.lock);
idr_for_each_entry(global_ft.idr, fp, id) {
if (!memcmp(fp->app_instance_id, app_instance_id,
SMB2_CREATE_GUID_SIZE)) {
fp = ksmbd_fp_get(fp);
break;
}
}
read_unlock(&global_ft.lock);
return fp;
}
int ksmbd_close_fd_app_instance_id(char *app_instance_id)
{
struct ksmbd_file_table *ft;
struct ksmbd_file *fp;
struct oplock_info *opinfo;
int n_to_drop = 0;
fp = ksmbd_lookup_fd_app_instance_id(app_instance_id);
if (!fp)
return 0;
opinfo = opinfo_get(fp);
if (!opinfo || !opinfo->sess)
goto out;
ft = &opinfo->sess->file_table;
write_lock(&ft->lock);
if (fp->f_state == FP_INITED) {
if (has_file_id(fp->volatile_id)) {
idr_remove(ft->idr, fp->volatile_id);
fp->volatile_id = KSMBD_NO_FID;
}
n_to_drop = ksmbd_mark_fp_closed(fp);
}
write_unlock(&ft->lock);
opinfo_put(opinfo);
opinfo = NULL;
if (!n_to_drop)
goto out;
down_write(&fp->f_ci->m_lock);
list_del_init(&fp->node);
up_write(&fp->f_ci->m_lock);
if (atomic_sub_and_test(n_to_drop, &fp->refcount)) {
if (fp->conn)
atomic_dec(&fp->conn->stats.open_files_count);
__ksmbd_close_fd(NULL, fp);
}
return 0;
out:
if (opinfo)
opinfo_put(opinfo);
ksmbd_put_durable_fd(fp);
return 0;
}
int ksmbd_invalidate_durable_fd(unsigned long long id)
{
struct ksmbd_file *fp;
fp = ksmbd_lookup_global_fd(id);
if (!fp)
return -ENOENT;
fp->durable_reconnect_disabled = true;
if (fp->conn) {
ksmbd_put_durable_fd(fp);
return -ENOENT;
}
fp->durable_timeout = 1;
fp->durable_scavenger_timeout = jiffies_to_msecs(jiffies);
ksmbd_put_durable_fd(fp);
if (waitqueue_active(&dh_wq))
wake_up(&dh_wq);
return -ENOENT;
}
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid)
{
struct ksmbd_file *fp = NULL;
@ -734,6 +944,30 @@ struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry)
return NULL;
}
bool ksmbd_has_open_files(struct dentry *dentry)
{
struct ksmbd_file *fp;
unsigned int id;
bool ret = false;
read_lock(&global_ft.lock);
idr_for_each_entry(global_ft.idr, fp, id) {
struct dentry *fp_dentry = fp->filp->f_path.dentry;
if (fp->f_state != FP_INITED)
continue;
if (fp_dentry == dentry)
continue;
if (is_subdir(fp_dentry, dentry)) {
ret = true;
break;
}
}
read_unlock(&global_ft.lock);
return ret;
}
#define OPEN_ID_TYPE_VOLATILE_ID (0)
#define OPEN_ID_TYPE_PERSISTENT_ID (1)
@ -758,7 +992,8 @@ static int __open_id(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
idr_preload(KSMBD_DEFAULT_GFP);
write_lock(&ft->lock);
ret = idr_alloc_cyclic(ft->idr, fp, 0, INT_MAX - 1, GFP_NOWAIT);
ret = idr_alloc_cyclic(ft->idr, fp, KSMBD_START_FID, INT_MAX - 1,
GFP_NOWAIT);
if (ret >= 0) {
id = ret;
ret = 0;
@ -929,6 +1164,7 @@ __close_file_table_ids(struct ksmbd_session *sess,
* global_ft.
*/
idr_remove(ft->idr, id);
fp->durable_volatile_id = fp->volatile_id;
fp->volatile_id = KSMBD_NO_FID;
write_unlock(&ft->lock);
@ -1108,10 +1344,10 @@ static int ksmbd_durable_scavenger(void *dummy)
if (try_to_freeze())
continue;
remaining_jiffies = wait_event_timeout(dh_wq,
remaining_jiffies = wait_event_interruptible_timeout(dh_wq,
ksmbd_durable_scavenger_alive() == false,
__msecs_to_jiffies(min_timeout));
if (remaining_jiffies)
if ((long)remaining_jiffies > 0)
min_timeout = jiffies_to_msecs(remaining_jiffies);
else
min_timeout = DURABLE_HANDLE_MAX_TIMEOUT;
@ -1303,9 +1539,15 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
up_write(&ci->m_lock);
list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
spin_lock(&conn->llist_lock);
struct ksmbd_conn *lock_conn = smb_lock->conn;
if (!lock_conn)
continue;
spin_lock(&lock_conn->llist_lock);
list_del_init(&smb_lock->clist);
spin_unlock(&conn->llist_lock);
smb_lock->conn = NULL;
spin_unlock(&lock_conn->llist_lock);
ksmbd_conn_put(lock_conn);
}
fp->conn = NULL;
@ -1435,6 +1677,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
}
list_for_each_entry(smb_lock, &fp->lock_list, flist) {
smb_lock->conn = ksmbd_conn_get(conn);
spin_lock(&conn->llist_lock);
list_add_tail(&smb_lock->clist, &conn->lock_list);
spin_unlock(&conn->llist_lock);

View File

@ -23,7 +23,12 @@
#define FILE_GENERIC_WRITE 0x120116
#define FILE_GENERIC_EXECUTE 0X1200a0
#define KSMBD_START_FID 0
/*
* Start volatile/persistent file id allocation at 1. A file id of 0 yields an
* SMB2 FileId of {0, 0}, which clients (e.g. Windows, Samba) treat as a null
* handle and never close, leaking the open on the server.
*/
#define KSMBD_START_FID 1
#define KSMBD_NO_FID (INT_MAX)
#define SMB2_NO_FID (0xFFFFFFFFFFFFFFFFULL)
@ -32,6 +37,7 @@ struct ksmbd_session;
struct ksmbd_lock {
struct file_lock *fl;
struct ksmbd_conn *conn;
struct list_head clist;
struct list_head flist;
struct list_head llist;
@ -80,6 +86,7 @@ struct ksmbd_file {
struct file *filp;
u64 persistent_id;
u64 volatile_id;
u64 durable_volatile_id;
spinlock_t f_lock;
@ -95,6 +102,8 @@ struct ksmbd_file {
__le32 coption;
__le32 cdoption;
__u64 create_time;
__u64 change_time;
__u64 allocation_size;
__u64 itime;
bool is_nt_open;
@ -121,6 +130,7 @@ struct ksmbd_file {
bool is_durable;
bool is_persistent;
bool is_resilient;
bool durable_reconnect_disabled;
bool is_posix_ctxt;
struct durable_owner owner;
@ -159,11 +169,17 @@ 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);
int ksmbd_invalidate_durable_fd(unsigned long long id);
bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp);
int ksmbd_close_fd_app_instance_id(char *app_instance_id);
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
bool ksmbd_has_open_files(struct dentry *dentry);
unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp);
void ksmbd_launch_ksmbd_durable_scavenger(void);