Skip to content

Commit 1e9cdc2

Browse files
committed
Merge tag 'v7.2-rc1-smb3-server-fixes' of git://git.samba.org/ksmbd
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
2 parents dac0b8c + f363a0f commit 1e9cdc2

9 files changed

Lines changed: 216 additions & 61 deletions

File tree

Documentation/filesystems/smb/ksmbd.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ ACLs Partially Supported. only DACLs available, SACLs
9797
to allow future support for running as a domain
9898
member.
9999
Kerberos Supported.
100-
Durable handle v1,v2 Planned for future.
100+
Durable handle v1,v2 Supported.
101101
Persistent handle Planned for future.
102102
SMB2 notify Planned for future.
103103
Sparse file support Supported.
@@ -111,7 +111,7 @@ DCE/RPC support Partially Supported. a few calls(NetShareEnumAll,
111111
for Witness protocol e.g.)
112112
ksmbd/nfsd interoperability Planned for future. The features that ksmbd
113113
support are Leases, Notify, ACLs and Share modes.
114-
SMB3.1.1 Compression Planned for future.
114+
SMB3.1.1 Compression Supported.
115115
SMB3.1.1 over QUIC Planned for future.
116116
Signing/Encryption over RDMA Planned for future.
117117
SMB3.1.1 GMAC signing support Planned for future.

fs/smb/server/ksmbd_work.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ struct ksmbd_work {
6767
/* Number of granted credits */
6868
unsigned int credits_granted;
6969

70+
/*
71+
* Credit charge added to conn->outstanding_credits at receive time
72+
* for the SMB2 PDU currently being processed, pending release. Zero
73+
* once the charge has been returned (on the response or error path).
74+
*/
75+
unsigned short credit_charge;
76+
7077
/* response smb header size */
7178
unsigned int response_sz;
7279

fs/smb/server/oplock.c

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,24 @@ struct oplock_info *opinfo_get(struct ksmbd_file *fp)
278278
return opinfo;
279279
}
280280

281-
static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
281+
struct oplock_snapshot {
282+
bool durable_open;
283+
bool durable_detached;
284+
unsigned long long fid;
285+
};
286+
287+
static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci,
288+
struct ksmbd_file *skip_fp,
289+
struct oplock_snapshot *snapshot)
282290
{
283291
struct oplock_info *opinfo;
284292

293+
if (snapshot) {
294+
snapshot->durable_open = false;
295+
snapshot->durable_detached = false;
296+
snapshot->fid = KSMBD_NO_FID;
297+
}
298+
285299
down_read(&ci->m_lock);
286300
opinfo = list_first_entry_or_null(&ci->m_op_list, struct oplock_info,
287301
op_entry);
@@ -295,6 +309,16 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
295309
opinfo = NULL;
296310
}
297311
}
312+
313+
if (opinfo && snapshot && opinfo->o_fp &&
314+
opinfo->o_fp != skip_fp &&
315+
READ_ONCE(opinfo->o_fp->is_durable)) {
316+
snapshot->durable_open = true;
317+
snapshot->durable_detached =
318+
!READ_ONCE(opinfo->o_fp->conn) ||
319+
!READ_ONCE(opinfo->o_fp->tcon);
320+
snapshot->fid = opinfo->fid;
321+
}
298322
}
299323
up_read(&ci->m_lock);
300324

@@ -314,7 +338,7 @@ void opinfo_put(struct oplock_info *opinfo)
314338

315339
static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
316340
{
317-
struct oplock_info *opinfo = opinfo_get_list(ci);
341+
struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL);
318342
bool is_lease;
319343

320344
if (!opinfo)
@@ -1180,6 +1204,36 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
11801204
return err;
11811205
}
11821206

1207+
struct oplock_break_entry {
1208+
struct list_head list;
1209+
struct oplock_info *opinfo;
1210+
};
1211+
1212+
static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo)
1213+
{
1214+
struct oplock_break_entry *ent;
1215+
1216+
ent = kmalloc_obj(struct oplock_break_entry, KSMBD_DEFAULT_GFP);
1217+
if (!ent)
1218+
return -ENOMEM;
1219+
1220+
ent->opinfo = opinfo;
1221+
list_add_tail(&ent->list, head);
1222+
return 0;
1223+
}
1224+
1225+
static void oplock_break_drain_none(struct list_head *head)
1226+
{
1227+
struct oplock_break_entry *ent, *tmp;
1228+
1229+
list_for_each_entry_safe(ent, tmp, head, list) {
1230+
oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
1231+
list_del(&ent->list);
1232+
opinfo_put(ent->opinfo);
1233+
kfree(ent);
1234+
}
1235+
}
1236+
11831237
void destroy_lease_table(struct ksmbd_conn *conn)
11841238
{
11851239
struct lease_table *lb, *lbtmp;
@@ -1289,6 +1343,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
12891343
{
12901344
struct oplock_info *opinfo;
12911345
struct ksmbd_inode *p_ci = NULL;
1346+
LIST_HEAD(brk_list);
12921347

12931348
if (lctx->version != 2)
12941349
return;
@@ -1314,19 +1369,22 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
13141369
continue;
13151370
}
13161371

1317-
oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
1318-
opinfo_put(opinfo);
1372+
if (oplock_break_add(&brk_list, opinfo))
1373+
opinfo_put(opinfo);
13191374
}
13201375
}
13211376
up_read(&p_ci->m_lock);
13221377

1378+
oplock_break_drain_none(&brk_list);
1379+
13231380
ksmbd_inode_put(p_ci);
13241381
}
13251382

13261383
void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
13271384
{
13281385
struct oplock_info *opinfo;
13291386
struct ksmbd_inode *p_ci = NULL;
1387+
LIST_HEAD(brk_list);
13301388

13311389
rcu_read_lock();
13321390
opinfo = rcu_dereference(fp->f_opinfo);
@@ -1355,12 +1413,14 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
13551413
continue;
13561414
}
13571415

1358-
oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
1359-
opinfo_put(opinfo);
1416+
if (oplock_break_add(&brk_list, opinfo))
1417+
opinfo_put(opinfo);
13601418
}
13611419
}
13621420
up_read(&p_ci->m_lock);
13631421

1422+
oplock_break_drain_none(&brk_list);
1423+
13641424
ksmbd_inode_put(p_ci);
13651425
}
13661426

@@ -1385,6 +1445,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
13851445
struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
13861446
struct ksmbd_inode *ci = fp->f_ci;
13871447
struct lease_table *new_lb = NULL;
1448+
struct oplock_snapshot prev_op_snapshot;
13881449
bool prev_op_has_lease;
13891450
bool prev_durable_open = false;
13901451
bool prev_durable_detached = false;
@@ -1452,7 +1513,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
14521513
goto out;
14531514
}
14541515
}
1455-
prev_opinfo = opinfo_get_list(ci);
1516+
prev_opinfo = opinfo_get_list(ci, fp, &prev_op_snapshot);
14561517
if (!prev_opinfo ||
14571518
(prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx)) {
14581519
opinfo_put(prev_opinfo);
@@ -1474,13 +1535,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
14741535
goto op_break_not_needed;
14751536
}
14761537

1477-
if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
1478-
prev_opinfo->o_fp->is_durable) {
1479-
prev_durable_open = true;
1480-
prev_durable_detached = !prev_opinfo->o_fp->conn ||
1481-
!prev_opinfo->o_fp->tcon;
1482-
prev_fid = prev_opinfo->fid;
1483-
}
1538+
prev_durable_open = prev_op_snapshot.durable_open;
1539+
prev_durable_detached = prev_op_snapshot.durable_detached;
1540+
prev_fid = prev_op_snapshot.fid;
14841541

14851542
err = oplock_break(prev_opinfo, break_level, work,
14861543
share_ret < 0 && prev_opinfo->is_lease);
@@ -1571,7 +1628,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
15711628
struct oplock_info *brk_opinfo;
15721629
bool sent_break = false;
15731630

1574-
brk_opinfo = opinfo_get_list(fp->f_ci);
1631+
brk_opinfo = opinfo_get_list(fp->f_ci, NULL, NULL);
15751632
if (!brk_opinfo)
15761633
return false;
15771634
if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
@@ -1602,9 +1659,11 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
16021659
bool send_interim, bool send_oplock_break)
16031660
{
16041661
struct oplock_info *op, *brk_op;
1662+
struct oplock_break_entry *ent, *tmp;
16051663
struct ksmbd_inode *ci;
16061664
struct ksmbd_conn *conn = work->conn;
16071665
bool sent_interim = false;
1666+
LIST_HEAD(brk_list);
16081667

16091668
if (!test_share_config_flag(work->tcon->share_conf,
16101669
KSMBD_SHARE_FLAG_OPLOCKS))
@@ -1646,6 +1705,22 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
16461705
SMB2_LEASE_KEY_SIZE))
16471706
goto next;
16481707
brk_op->open_trunc = is_trunc;
1708+
1709+
/*
1710+
* Defer the break until ci->m_lock is released: oplock_break()
1711+
* may block waiting for the lease break acknowledgment, and the
1712+
* close that wakes that wait needs ci->m_lock for write.
1713+
*/
1714+
if (!oplock_break_add(&brk_list, brk_op))
1715+
continue;
1716+
next:
1717+
opinfo_put(brk_op);
1718+
}
1719+
up_read(&ci->m_lock);
1720+
1721+
list_for_each_entry_safe(ent, tmp, &brk_list, list) {
1722+
brk_op = ent->opinfo;
1723+
16491724
if (!brk_op->is_lease && !send_oplock_break) {
16501725
brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
16511726
brk_op->op_state = OPLOCK_STATE_NONE;
@@ -1657,10 +1732,10 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
16571732
false);
16581733
}
16591734
sent_interim = true;
1660-
next:
1735+
list_del(&ent->list);
16611736
opinfo_put(brk_op);
1737+
kfree(ent);
16621738
}
1663-
up_read(&ci->m_lock);
16641739

16651740
if (op)
16661741
opinfo_put(op);

fs/smb/server/server.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,20 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
242242
} while (is_chained == true);
243243

244244
send:
245+
/*
246+
* Release any credit charge still outstanding for this request. On
247+
* the normal path smb2_set_rsp_credits() already returned it, but the
248+
* abort, error and send-no-response paths skip that call, so the
249+
* charge would otherwise leak and eventually exhaust the connection's
250+
* outstanding credit window.
251+
*/
252+
if (work->credit_charge) {
253+
spin_lock(&conn->credits_lock);
254+
conn->outstanding_credits -= work->credit_charge;
255+
work->credit_charge = 0;
256+
spin_unlock(&conn->credits_lock);
257+
}
258+
245259
if (work->tcon)
246260
ksmbd_tree_connect_put(work->tcon);
247261
smb3_preauth_hash_rsp(work);

fs/smb/server/smb2misc.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,12 @@ static int smb2_calc_size(void *buf, unsigned int *len)
261261

262262
static inline int smb2_query_info_req_len(struct smb2_query_info_req *h)
263263
{
264-
return le32_to_cpu(h->InputBufferLength) +
265-
le32_to_cpu(h->OutputBufferLength);
264+
return le32_to_cpu(h->InputBufferLength);
265+
}
266+
267+
static inline int smb2_query_info_resp_len(struct smb2_query_info_req *h)
268+
{
269+
return le32_to_cpu(h->OutputBufferLength);
266270
}
267271

268272
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)
297301
le32_to_cpu(h->MaxOutputResponse);
298302
}
299303

300-
static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
304+
static int smb2_validate_credit_charge(struct ksmbd_work *work,
301305
struct smb2_hdr *hdr)
302306
{
307+
struct ksmbd_conn *conn = work->conn;
303308
unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
304309
unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge);
305310
void *__hdr = hdr;
@@ -308,6 +313,7 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
308313
switch (hdr->Command) {
309314
case SMB2_QUERY_INFO:
310315
req_len = smb2_query_info_req_len(__hdr);
316+
expect_resp_len = smb2_query_info_resp_len(__hdr);
311317
break;
312318
case SMB2_SET_INFO:
313319
req_len = smb2_set_info_req_len(__hdr);
@@ -356,8 +362,10 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
356362
ksmbd_debug(SMB, "Limits exceeding the maximum allowable outstanding requests, given : %u, pending : %u\n",
357363
credit_charge, conn->outstanding_credits);
358364
ret = 1;
359-
} else
365+
} else {
360366
conn->outstanding_credits += credit_charge;
367+
work->credit_charge = credit_charge;
368+
}
361369

362370
spin_unlock(&conn->credits_lock);
363371

@@ -460,7 +468,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
460468

461469
validate_credit:
462470
if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
463-
smb2_validate_credit_charge(work->conn, hdr))
471+
smb2_validate_credit_charge(work, hdr))
464472
return 1;
465473

466474
return 0;

fs/smb/server/smb2pdu.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
363363

364364
conn->total_credits -= credit_charge;
365365
conn->outstanding_credits -= credit_charge;
366+
work->credit_charge = 0;
366367
credits_requested = max_t(unsigned short,
367368
le16_to_cpu(req_hdr->CreditRequest), 1);
368369

@@ -6920,16 +6921,18 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
69206921
}
69216922
case FILE_LINK_INFORMATION:
69226923
{
6923-
if (!(fp->daccess & FILE_DELETE_LE)) {
6924-
pr_err("no right to delete : 0x%x\n", fp->daccess);
6925-
return -EACCES;
6926-
}
6924+
struct smb2_file_link_info *file_info;
69276925

69286926
if (buf_len < sizeof(struct smb2_file_link_info))
69296927
return -EMSGSIZE;
69306928

6931-
return smb2_create_link(work, work->tcon->share_conf,
6932-
(struct smb2_file_link_info *)buffer,
6929+
file_info = (struct smb2_file_link_info *)buffer;
6930+
if (file_info->ReplaceIfExists && !(fp->daccess & FILE_DELETE_LE)) {
6931+
pr_err("no right to delete : 0x%x\n", fp->daccess);
6932+
return -EACCES;
6933+
}
6934+
6935+
return smb2_create_link(work, work->tcon->share_conf, file_info,
69336936
buf_len, fp->filp,
69346937
work->conn->local_nls);
69356938
}
@@ -7324,7 +7327,7 @@ int smb2_read(struct ksmbd_work *work)
73247327
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
73257328
fp->filp, offset, length);
73267329

7327-
aux_payload_buf = kvzalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
7330+
aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
73287331
if (!aux_payload_buf) {
73297332
err = -ENOMEM;
73307333
goto out;

0 commit comments

Comments
 (0)