diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-07-03 18:55:34 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-07-03 18:55:34 -1000 |
| commit | 1e9cdc2ea15adf4a821eefedabf6c0c8cf0b6a55 (patch) | |
| tree | c81f3227b4965d9139a1adcb53f1168b7a391315 | |
| parent | dac0b8c58757eba9deb0fdd32d37a85bbb06006d (diff) | |
| parent | f363a0fb134a3eb9e47368b1edbd251fd76be84b (diff) | |
Pull smb server fixes from Steve French:
- Fix several use-after-free races in durable handle reconnect,
supersede, and oplock handling
- Avoid holding the inode oplock lock while waiting for a lease break
acknowledgement. This removes delays of up to 35 seconds when cifs.ko
closes a deferred handle in response to a lease break
- Fix malformed security descriptor handling, including an undersized
DACL allocation issue and an out-of-bounds ACE SID read
- Fix memory leaks in security descriptor and DOS attribute xattr
encoding/decoding error paths
- Fix outstanding SMB2 credit leaks on aborted requests and correct the
QUERY_INFO credit charge calculation
- Fix hard-link creation without replacement being incorrectly rejected
when the handle lacks DELETE access
- Avoid unnecessary zeroing of large SMB2 read buffers
- Add an oplock list lockdep annotation and update the documented
support status for durable handles and SMB3.1.1 compression
- Durable handle fixes to address ownership and lifetime races during
reconnect, session teardown, oplock handling, and superseding opens,
preventing stale session and file references from being used by
concurrent operations
* tag 'v7.2-rc1-smb3-server-fixes' of git://git.samba.org/ksmbd:
ksmbd: fix app-instance durable supersede session UAF
ksmbd: snapshot previous oplock state before durable checks
ksmbd: close superseded durable handles through refcount handoff
ksmbd: fix use-after-free of fp->owner.name in durable handle owner check
smb/server: do not require delete access for non-replacing links
ksmbd: don't hold ci->m_lock while waiting for a lease break ack
ksmbd: doc: update feature support status for durable handles and compression
ksmbd: annotate oplock list traversals under m_lock
ksmbd: fix outstanding credit leak on abort and error paths
ksmbd: fix credit charge calculation for SMB2 QUERY_INFO
ksmbd: avoid zeroing the read buffer in smb2_read()
ksmbd: validate num_subauth when copying ACE in set_ntacl_dacl
ksmbd: reject undersized DACLs before parsing ACEs
ksmbd: fix n.data memory leak in ksmbd_vfs_set_dos_attrib_xattr
ksmbd: Fix acl.sd_buf memory leak and invalid sd_size error handling
ksmbd: fix sd_ndr.data memory leak in ksmbd_vfs_set_sd_xattr
| -rw-r--r-- | Documentation/filesystems/smb/ksmbd.rst | 4 | ||||
| -rw-r--r-- | fs/smb/server/ksmbd_work.h | 7 | ||||
| -rw-r--r-- | fs/smb/server/oplock.c | 109 | ||||
| -rw-r--r-- | fs/smb/server/server.c | 14 | ||||
| -rw-r--r-- | fs/smb/server/smb2misc.c | 18 | ||||
| -rw-r--r-- | fs/smb/server/smb2pdu.c | 17 | ||||
| -rw-r--r-- | fs/smb/server/smbacl.c | 15 | ||||
| -rw-r--r-- | fs/smb/server/vfs.c | 14 | ||||
| -rw-r--r-- | fs/smb/server/vfs_cache.c | 79 |
9 files changed, 216 insertions, 61 deletions
diff --git a/Documentation/filesystems/smb/ksmbd.rst b/Documentation/filesystems/smb/ksmbd.rst index 67cb68ea6e68..672c5d3892ff 100644 --- a/Documentation/filesystems/smb/ksmbd.rst +++ b/Documentation/filesystems/smb/ksmbd.rst @@ -97,7 +97,7 @@ ACLs Partially Supported. only DACLs available, SACLs to allow future support for running as a domain member. Kerberos Supported. -Durable handle v1,v2 Planned for future. +Durable handle v1,v2 Supported. Persistent handle Planned for future. SMB2 notify Planned for future. Sparse file support Supported. @@ -111,7 +111,7 @@ DCE/RPC support Partially Supported. a few calls(NetShareEnumAll, for Witness protocol e.g.) ksmbd/nfsd interoperability Planned for future. The features that ksmbd support are Leases, Notify, ACLs and Share modes. -SMB3.1.1 Compression Planned for future. +SMB3.1.1 Compression Supported. SMB3.1.1 over QUIC Planned for future. Signing/Encryption over RDMA Planned for future. SMB3.1.1 GMAC signing support Planned for future. diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h index df0554a2c50d..88104f0cf363 100644 --- a/fs/smb/server/ksmbd_work.h +++ b/fs/smb/server/ksmbd_work.h @@ -67,6 +67,13 @@ struct ksmbd_work { /* Number of granted credits */ unsigned int credits_granted; + /* + * Credit charge added to conn->outstanding_credits at receive time + * for the SMB2 PDU currently being processed, pending release. Zero + * once the charge has been returned (on the response or error path). + */ + unsigned short credit_charge; + /* response smb header size */ unsigned int response_sz; diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 31dd9f3479b2..3c55ae5d6a11 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -278,10 +278,24 @@ struct oplock_info *opinfo_get(struct ksmbd_file *fp) return opinfo; } -static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci) +struct oplock_snapshot { + bool durable_open; + bool durable_detached; + unsigned long long fid; +}; + +static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci, + struct ksmbd_file *skip_fp, + struct oplock_snapshot *snapshot) { struct oplock_info *opinfo; + if (snapshot) { + snapshot->durable_open = false; + snapshot->durable_detached = false; + snapshot->fid = KSMBD_NO_FID; + } + down_read(&ci->m_lock); opinfo = list_first_entry_or_null(&ci->m_op_list, struct oplock_info, op_entry); @@ -295,6 +309,16 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci) opinfo = NULL; } } + + if (opinfo && snapshot && opinfo->o_fp && + opinfo->o_fp != skip_fp && + READ_ONCE(opinfo->o_fp->is_durable)) { + snapshot->durable_open = true; + snapshot->durable_detached = + !READ_ONCE(opinfo->o_fp->conn) || + !READ_ONCE(opinfo->o_fp->tcon); + snapshot->fid = opinfo->fid; + } } up_read(&ci->m_lock); @@ -314,7 +338,7 @@ void opinfo_put(struct oplock_info *opinfo) static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci) { - struct oplock_info *opinfo = opinfo_get_list(ci); + struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL); bool is_lease; if (!opinfo) @@ -1180,6 +1204,36 @@ again: return err; } +struct oplock_break_entry { + struct list_head list; + struct oplock_info *opinfo; +}; + +static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo) +{ + struct oplock_break_entry *ent; + + ent = kmalloc_obj(struct oplock_break_entry, KSMBD_DEFAULT_GFP); + if (!ent) + return -ENOMEM; + + ent->opinfo = opinfo; + list_add_tail(&ent->list, head); + return 0; +} + +static void oplock_break_drain_none(struct list_head *head) +{ + struct oplock_break_entry *ent, *tmp; + + list_for_each_entry_safe(ent, tmp, head, list) { + oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false); + list_del(&ent->list); + opinfo_put(ent->opinfo); + kfree(ent); + } +} + void destroy_lease_table(struct ksmbd_conn *conn) { struct lease_table *lb, *lbtmp; @@ -1289,6 +1343,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp, { struct oplock_info *opinfo; struct ksmbd_inode *p_ci = NULL; + LIST_HEAD(brk_list); if (lctx->version != 2) return; @@ -1314,12 +1369,14 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp, continue; } - oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false); - opinfo_put(opinfo); + if (oplock_break_add(&brk_list, opinfo)) + opinfo_put(opinfo); } } up_read(&p_ci->m_lock); + oplock_break_drain_none(&brk_list); + ksmbd_inode_put(p_ci); } @@ -1327,6 +1384,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp) { struct oplock_info *opinfo; struct ksmbd_inode *p_ci = NULL; + LIST_HEAD(brk_list); rcu_read_lock(); opinfo = rcu_dereference(fp->f_opinfo); @@ -1355,12 +1413,14 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp) continue; } - oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false); - opinfo_put(opinfo); + if (oplock_break_add(&brk_list, opinfo)) + opinfo_put(opinfo); } } up_read(&p_ci->m_lock); + oplock_break_drain_none(&brk_list); + ksmbd_inode_put(p_ci); } @@ -1385,6 +1445,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid, struct oplock_info *opinfo = NULL, *prev_opinfo = NULL; struct ksmbd_inode *ci = fp->f_ci; struct lease_table *new_lb = NULL; + struct oplock_snapshot prev_op_snapshot; bool prev_op_has_lease; bool prev_durable_open = false; bool prev_durable_detached = false; @@ -1452,7 +1513,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid, goto out; } } - prev_opinfo = opinfo_get_list(ci); + prev_opinfo = opinfo_get_list(ci, fp, &prev_op_snapshot); if (!prev_opinfo || (prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx)) { opinfo_put(prev_opinfo); @@ -1474,13 +1535,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid, goto op_break_not_needed; } - if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp && - prev_opinfo->o_fp->is_durable) { - prev_durable_open = true; - prev_durable_detached = !prev_opinfo->o_fp->conn || - !prev_opinfo->o_fp->tcon; - prev_fid = prev_opinfo->fid; - } + prev_durable_open = prev_op_snapshot.durable_open; + prev_durable_detached = prev_op_snapshot.durable_detached; + prev_fid = prev_op_snapshot.fid; err = oplock_break(prev_opinfo, break_level, work, share_ret < 0 && prev_opinfo->is_lease); @@ -1571,7 +1628,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work, struct oplock_info *brk_opinfo; bool sent_break = false; - brk_opinfo = opinfo_get_list(fp->f_ci); + brk_opinfo = opinfo_get_list(fp->f_ci, NULL, NULL); if (!brk_opinfo) return false; if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH && @@ -1602,9 +1659,11 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work, bool send_interim, bool send_oplock_break) { struct oplock_info *op, *brk_op; + struct oplock_break_entry *ent, *tmp; struct ksmbd_inode *ci; struct ksmbd_conn *conn = work->conn; bool sent_interim = false; + LIST_HEAD(brk_list); if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS)) @@ -1646,6 +1705,22 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work, SMB2_LEASE_KEY_SIZE)) goto next; brk_op->open_trunc = is_trunc; + + /* + * Defer the break until ci->m_lock is released: oplock_break() + * may block waiting for the lease break acknowledgment, and the + * close that wakes that wait needs ci->m_lock for write. + */ + if (!oplock_break_add(&brk_list, brk_op)) + continue; +next: + opinfo_put(brk_op); + } + up_read(&ci->m_lock); + + list_for_each_entry_safe(ent, tmp, &brk_list, list) { + brk_op = ent->opinfo; + if (!brk_op->is_lease && !send_oplock_break) { brk_op->level = SMB2_OPLOCK_LEVEL_NONE; brk_op->op_state = OPLOCK_STATE_NONE; @@ -1657,10 +1732,10 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work, false); } sent_interim = true; -next: + list_del(&ent->list); opinfo_put(brk_op); + kfree(ent); } - up_read(&ci->m_lock); if (op) opinfo_put(op); diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index 36feda7e0942..36a5ea4828ad 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -242,6 +242,20 @@ static void __handle_ksmbd_work(struct ksmbd_work *work, } while (is_chained == true); send: + /* + * Release any credit charge still outstanding for this request. On + * the normal path smb2_set_rsp_credits() already returned it, but the + * abort, error and send-no-response paths skip that call, so the + * charge would otherwise leak and eventually exhaust the connection's + * outstanding credit window. + */ + if (work->credit_charge) { + spin_lock(&conn->credits_lock); + conn->outstanding_credits -= work->credit_charge; + work->credit_charge = 0; + spin_unlock(&conn->credits_lock); + } + if (work->tcon) ksmbd_tree_connect_put(work->tcon); smb3_preauth_hash_rsp(work); diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c index a1ddca21c47b..c0c4edd092c2 100644 --- a/fs/smb/server/smb2misc.c +++ b/fs/smb/server/smb2misc.c @@ -261,8 +261,12 @@ calc_size_exit: static inline int smb2_query_info_req_len(struct smb2_query_info_req *h) { - return le32_to_cpu(h->InputBufferLength) + - le32_to_cpu(h->OutputBufferLength); + return le32_to_cpu(h->InputBufferLength); +} + +static inline int smb2_query_info_resp_len(struct smb2_query_info_req *h) +{ + return le32_to_cpu(h->OutputBufferLength); } static inline int smb2_set_info_req_len(struct smb2_set_info_req *h) @@ -297,9 +301,10 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h) le32_to_cpu(h->MaxOutputResponse); } -static int smb2_validate_credit_charge(struct ksmbd_conn *conn, +static int smb2_validate_credit_charge(struct ksmbd_work *work, struct smb2_hdr *hdr) { + struct ksmbd_conn *conn = work->conn; unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len; unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge); void *__hdr = hdr; @@ -308,6 +313,7 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn, switch (hdr->Command) { case SMB2_QUERY_INFO: req_len = smb2_query_info_req_len(__hdr); + expect_resp_len = smb2_query_info_resp_len(__hdr); break; case SMB2_SET_INFO: req_len = smb2_set_info_req_len(__hdr); @@ -356,8 +362,10 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn, ksmbd_debug(SMB, "Limits exceeding the maximum allowable outstanding requests, given : %u, pending : %u\n", credit_charge, conn->outstanding_credits); ret = 1; - } else + } else { conn->outstanding_credits += credit_charge; + work->credit_charge = credit_charge; + } spin_unlock(&conn->credits_lock); @@ -460,7 +468,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work) validate_credit: if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) && - smb2_validate_credit_charge(work->conn, hdr)) + smb2_validate_credit_charge(work, hdr)) return 1; return 0; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 5859fa68bb84..097f51fc7ed6 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -363,6 +363,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work) conn->total_credits -= credit_charge; conn->outstanding_credits -= credit_charge; + work->credit_charge = 0; credits_requested = max_t(unsigned short, le16_to_cpu(req_hdr->CreditRequest), 1); @@ -6920,16 +6921,18 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, } case FILE_LINK_INFORMATION: { - if (!(fp->daccess & FILE_DELETE_LE)) { - pr_err("no right to delete : 0x%x\n", fp->daccess); - return -EACCES; - } + struct smb2_file_link_info *file_info; if (buf_len < sizeof(struct smb2_file_link_info)) return -EMSGSIZE; - return smb2_create_link(work, work->tcon->share_conf, - (struct smb2_file_link_info *)buffer, + file_info = (struct smb2_file_link_info *)buffer; + if (file_info->ReplaceIfExists && !(fp->daccess & FILE_DELETE_LE)) { + pr_err("no right to delete : 0x%x\n", fp->daccess); + return -EACCES; + } + + return smb2_create_link(work, work->tcon->share_conf, file_info, buf_len, fp->filp, work->conn->local_nls); } @@ -7324,7 +7327,7 @@ int smb2_read(struct ksmbd_work *work) ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n", fp->filp, offset, length); - aux_payload_buf = kvzalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP); + aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP); if (!aux_payload_buf) { err = -ENOMEM; goto out; diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 340ea98fa494..9c59c8f73b66 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -374,6 +374,7 @@ static void parse_dacl(struct mnt_idmap *idmap, { int i, ret; u16 num_aces = 0; + u16 dacl_size; unsigned int acl_size; char *acl_base; struct smb_ace **ppace; @@ -403,7 +404,11 @@ static void parse_dacl(struct mnt_idmap *idmap, if (num_aces <= 0) return; - if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) / + dacl_size = le16_to_cpu(pdacl->size); + if (dacl_size < sizeof(struct smb_acl)) + return; + + if (num_aces > (dacl_size - sizeof(struct smb_acl)) / (offsetof(struct smb_ace, sid) + offsetof(struct smb_sid, sub_auth) + sizeof(__le16))) return; @@ -740,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, if (nt_ace_size > aces_size) break; + if (ntace->sid.num_subauth == 0 || + ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) + goto next_ace; + memcpy((char *)pndace + size, ntace, nt_ace_size); if (check_add_overflow(size, nt_ace_size, &size)) break; + num_aces++; + +next_ace: aces_size -= nt_ace_size; ntace = (struct smb_ace *)((char *)ntace + nt_ace_size); - num_aces++; } } diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index f5fa22d87603..d0a0ad15d803 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -1487,8 +1487,8 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn, if (rc < 0) pr_err("Failed to store XATTR ntacl :%d\n", rc); - kfree(sd_ndr.data); out: + kfree(sd_ndr.data); kfree(acl_ndr.data); kfree(smb_acl); kfree(def_smb_acl); @@ -1504,7 +1504,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, struct ndr n; struct inode *inode = d_inode(dentry); struct ndr acl_ndr = {0}; - struct xattr_ntacl acl; + struct xattr_ntacl acl = {0}; struct xattr_smb_acl *smb_acl = NULL, *def_smb_acl = NULL; __u8 cmp_hash[XATTR_SD_HASH_SIZE] = {0}; @@ -1515,7 +1515,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, n.length = rc; rc = ndr_decode_v4_ntacl(&n, &acl); if (rc) - goto free_n_data; + goto out_free; smb_acl = ksmbd_vfs_make_xattr_posix_acl(idmap, inode, ACL_TYPE_ACCESS); @@ -1541,6 +1541,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, *pntsd = acl.sd_buf; if (acl.sd_size < sizeof(struct smb_ntsd)) { pr_err("sd size is invalid\n"); + rc = -EINVAL; goto out_free; } @@ -1560,8 +1561,6 @@ out_free: kfree(acl.sd_buf); *pntsd = NULL; } - -free_n_data: kfree(n.data); return rc; } @@ -1576,14 +1575,15 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap, err = ndr_encode_dos_attr(&n, da); if (err) - return err; + goto out; err = ksmbd_vfs_setxattr(idmap, path, XATTR_NAME_DOS_ATTRIBUTE, (void *)n.data, n.offset, 0, get_write); if (err) ksmbd_debug(SMB, "failed to store dos attribute in xattr\n"); - kfree(n.data); +out: + kfree(n.data); return err; } diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index fde22742d193..d95c405eab11 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -581,7 +581,20 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry) if (fp->conn || !fp->is_durable || fp->f_state != FP_INITED) continue; - list_move_tail(&fp->node, &dispose); + + /* + * Claim the close before unlinking fp from m_fp_list. + * refcount == 1 means only the durable lifetime ref is + * left. Add a transient ref so final close can drop both. + */ + write_lock(&global_ft.lock); + if (atomic_read(&fp->refcount) == 1) { + atomic_inc(&fp->refcount); + __ksmbd_remove_durable_fd(fp); + ksmbd_mark_fp_closed(fp); + list_move_tail(&fp->node, &dispose); + } + write_unlock(&global_ft.lock); } } up_write(&ci->m_lock); @@ -589,16 +602,18 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry) /* * 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. + * collected handles still hold the transient reference taken above, 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; + if (atomic_sub_and_test(2, &fp->refcount)) { + __ksmbd_close_fd(NULL, fp); + closed = true; + } } return closed; @@ -838,19 +853,24 @@ int ksmbd_close_fd_app_instance_id(char *app_instance_id) return 0; opinfo = opinfo_get(fp); - if (!opinfo || !opinfo->sess) + if (!opinfo) goto out; + down_read(&fp->f_ci->m_lock); + if (!opinfo->conn) { + up_read(&fp->f_ci->m_lock); + 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; - } + if (fp->f_state == FP_INITED && 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); + up_read(&fp->f_ci->m_lock); opinfo_put(opinfo); opinfo = NULL; @@ -1461,16 +1481,21 @@ void ksmbd_stop_durable_scavenger(void) static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp, struct ksmbd_user *user) { + char *name; + if (!user) return -EINVAL; /* Duplicate the user name to ensure identity persistence */ - fp->owner.name = kstrdup(user->name, GFP_KERNEL); - if (!fp->owner.name) + name = kstrdup(user->name, GFP_KERNEL); + if (!name) return -ENOMEM; + spin_lock(&fp->f_lock); fp->owner.uid = user->uid; fp->owner.gid = user->gid; + fp->owner.name = name; + spin_unlock(&fp->f_lock); return 0; } @@ -1488,18 +1513,24 @@ static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp, bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp, struct ksmbd_user *user) { - if (!user || !fp->owner.name) + bool ret = false; + + if (!user) return false; + spin_lock(&fp->f_lock); + if (!fp->owner.name) + goto out; + /* Check if the UID and GID match first (fast path) */ if (fp->owner.uid != user->uid || fp->owner.gid != user->gid) - return false; + goto out; /* Validate the account name to ensure the same SecurityContext */ - if (strcmp(fp->owner.name, user->name)) - return false; - - return true; + ret = (strcmp(fp->owner.name, user->name) == 0); +out: + spin_unlock(&fp->f_lock); + return ret; } static bool session_fd_check(struct ksmbd_tree_connect *tcon, @@ -1530,11 +1561,13 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, conn = fp->conn; ci = fp->f_ci; down_write(&ci->m_lock); - list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry, + lockdep_is_held(&ci->m_lock)) { if (op->conn != conn) continue; ksmbd_conn_put(op->conn); op->conn = NULL; + op->sess = NULL; } up_write(&ci->m_lock); @@ -1685,16 +1718,20 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) ci = fp->f_ci; down_write(&ci->m_lock); - list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry, + lockdep_is_held(&ci->m_lock)) { if (op->conn) continue; op->conn = ksmbd_conn_get(fp->conn); + op->sess = work->sess; } up_write(&ci->m_lock); + spin_lock(&fp->f_lock); fp->owner.uid = fp->owner.gid = 0; kfree(fp->owner.name); fp->owner.name = NULL; + spin_unlock(&fp->f_lock); return 0; } |
