Print this page
NEX-15931 Panic removing files in SMB3 CA share
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Include in backports of:
NEX-9808 SMB3 persistent handles
NEX-15931 Panic removing files in SMB3 CA share
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Include in backports of:
NEX-9808 SMB3 persistent handles
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15555 SMB2 async redesign
NEX-15061 smtorture smb2.lock.cancel.cancel is failed
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Also follow-up change to:
NEX-1643 dtrace provider for smbsrv (remove "done2" probes,
which don't make sense with the new async design)
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15555 SMB2 async redesign
NEX-15061 smtorture smb2.lock.cancel.cancel is failed
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Also follow-up change to:
NEX-1643 dtrace provider for smbsrv (remove "done2" probes,
which don't make sense with the new async design)
NEX-1643 dtrace provider for smbsrv
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-6042 SMB resilient handle lock replay
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-6041 Should pass the smbtorture lock tests
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
SMB-122 smbd core dumps in smbd_dc_update / smb_log
SMB-117 Win7 fails to open security properties
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
*** 8,59 ****
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
! * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Dispatch function for SMB2_LOCK
*/
#include <smbsrv/smb2_kproto.h>
! struct SMB2_LOCK_ELEMENT {
uint64_t Offset;
uint64_t Length;
uint32_t Flags;
uint32_t reserved;
! };
! static smb_sdrc_t smb2_lock_async(smb_request_t *);
! static uint32_t smb2_lock_exec(smb_request_t *, uint16_t);
! static uint32_t smb2_lock_elem(smb_request_t *, struct SMB2_LOCK_ELEMENT *);
/*
* This is a somewhat arbitrary sanity limit on the length of the
* SMB2_LOCK_ELEMENT array. It usually has length one or two.
*/
int smb2_lock_max_elem = 1024;
smb_sdrc_t
smb2_lock(smb_request_t *sr)
{
! struct SMB2_LOCK_ELEMENT elem;
smb2fid_t smb2fid;
- uint32_t save_offset;
uint32_t LockSequence;
uint32_t status;
uint16_t StructSize;
uint16_t LockCount;
uint16_t i;
! boolean_t MayBlock = B_FALSE;
! int rc = 0;
/*
! * SMB2 Lock request
*/
rc = smb_mbc_decodef(
&sr->smb_data, "wwlqq",
&StructSize, /* w */
&LockCount, /* w */
--- 8,66 ----
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
! * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Dispatch function for SMB2_LOCK
*/
#include <smbsrv/smb2_kproto.h>
! /*
! * [MS-SMB2] 2.2.26 LockSequenceIndex, LockSequenceNumber.
! */
! #define SMB2_LSN_SHIFT 4
! #define SMB2_LSN_MASK 0xf
!
! typedef struct SMB2_LOCK_ELEMENT {
uint64_t Offset;
uint64_t Length;
uint32_t Flags;
uint32_t reserved;
! } lock_elem_t;
! static uint32_t smb2_unlock(smb_request_t *);
! static uint32_t smb2__lock(smb_request_t *);
! static uint32_t smb2_lock_blocking(smb_request_t *);
+ static boolean_t smb2_lock_chk_lockseq(smb_ofile_t *, uint32_t);
+ static void smb2_lock_set_lockseq(smb_ofile_t *, uint32_t);
+
/*
* This is a somewhat arbitrary sanity limit on the length of the
* SMB2_LOCK_ELEMENT array. It usually has length one or two.
*/
int smb2_lock_max_elem = 1024;
smb_sdrc_t
smb2_lock(smb_request_t *sr)
{
! lock_elem_t *lvec, *lk;
smb2fid_t smb2fid;
uint32_t LockSequence;
uint32_t status;
uint16_t StructSize;
uint16_t LockCount;
uint16_t i;
! int rc;
/*
! * Decode SMB2 Lock request
*/
rc = smb_mbc_decodef(
&sr->smb_data, "wwlqq",
&StructSize, /* w */
&LockCount, /* w */
*** 61,73 ****
&smb2fid.persistent, /* q */
&smb2fid.temporal); /* q */
if (rc || StructSize != 48)
return (SDRC_ERROR);
status = smb2sr_lookup_fid(sr, &smb2fid);
if (status)
! goto errout;
if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
if (LockCount > smb2_lock_max_elem) {
--- 68,85 ----
&smb2fid.persistent, /* q */
&smb2fid.temporal); /* q */
if (rc || StructSize != 48)
return (SDRC_ERROR);
+ /*
+ * Want FID lookup before the start probe.
+ */
status = smb2sr_lookup_fid(sr, &smb2fid);
+ DTRACE_SMB2_START(op__Lock, smb_request_t *, sr);
+
if (status)
! goto errout; /* Bad FID */
if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
if (LockCount > smb2_lock_max_elem) {
*** 74,293 ****
status = NT_STATUS_INSUFFICIENT_RESOURCES;
goto errout;
}
/*
! * Process the array of SMB2_LOCK_ELEMENT structs
! * We do this twice. (it's always a short list)
! * The first time, just validate the flags, and check
! * if any of the locking request might need to block.
! * The second time (either here, or in the async
! * handler function) process the locks for real.
*/
! save_offset = sr->smb_data.chain_offset;
! for (i = 0; i < LockCount; i++) {
! rc = smb_mbc_decodef(
! &sr->smb_data, "qqll",
! &elem.Offset, /* q */
! &elem.Length, /* q */
! &elem.Flags, /* l */
! &elem.reserved); /* l */
! if (rc) {
! status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
/*
! * Make sure the flags are valid;
! * Find out if we might block.
*/
! switch (elem.Flags) {
! case SMB2_LOCKFLAG_SHARED_LOCK:
! case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
! MayBlock = B_TRUE;
! break;
!
! /* BEGIN CSTYLED */
! case SMB2_LOCKFLAG_SHARED_LOCK |
! SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
! case SMB2_LOCKFLAG_EXCLUSIVE_LOCK |
! SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
! case SMB2_LOCKFLAG_UNLOCK:
! /* END CSTYLED */
! break;
!
! default:
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
}
- if (MayBlock) {
/*
! * May need to block. "Go async".
*/
! status = smb2sr_go_async(sr, smb2_lock_async);
! goto errout;
}
! sr->smb_data.chain_offset = save_offset;
! status = smb2_lock_exec(sr, LockCount);
! if (status)
! goto errout;
/*
! * SMB2 Lock reply (sync)
*/
- StructSize = 4;
(void) smb_mbc_encodef(
&sr->reply, "w..",
! StructSize); /* w */
/* reserved .. */
return (SDRC_SUCCESS);
-
- errout:
- smb2sr_put_error(sr, status);
- return (SDRC_SUCCESS);
}
! static smb_sdrc_t
! smb2_lock_async(smb_request_t *sr)
{
! smb2fid_t smb2fid;
! uint32_t LockSequence;
! uint32_t status;
! uint16_t StructSize;
! uint16_t LockCount;
! int rc = 0;
! /*
! * Decode the lock request again. It should all decode
! * exactly the same as the first time we saw it. If not,
! * report an "internal error".
! */
! rc = smb_mbc_decodef(
! &sr->smb_data, "wwlqq",
! &StructSize, /* w */
! &LockCount, /* w */
! &LockSequence, /* l */
! &smb2fid.persistent, /* q */
! &smb2fid.temporal); /* q */
! if (rc || StructSize != 48)
! return (SDRC_ERROR);
! status = smb2sr_lookup_fid(sr, &smb2fid);
! if (status)
! goto errout;
! if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
! status = NT_STATUS_INTERNAL_ERROR;
! goto errout;
}
! status = smb2_lock_exec(sr, LockCount);
! if (status)
! goto errout;
! /*
! * SMB2 Lock reply (async)
! */
! StructSize = 4;
! (void) smb_mbc_encodef(
! &sr->reply, "w..",
! StructSize); /* w */
! /* reserved .. */
! return (SDRC_SUCCESS);
!
! errout:
! smb2sr_put_error(sr, status);
! return (SDRC_SUCCESS);
}
/*
! * Execute the vector of locks. This is the common function called by
! * either the sync or async code paths. We've already decoded this
! * request once when we get here, so if there are any decode errors
! * then it's some kind of internal error.
*/
static uint32_t
! smb2_lock_exec(smb_request_t *sr, uint16_t LockCount)
{
! struct SMB2_LOCK_ELEMENT elem;
uint32_t status = 0;
- uint16_t i;
- int rc;
! /*
! * On entry, out position in the input data should be
! * after both the SMB2 header and the fixed part of
! * the SMB Lock request header (24).
! */
! ASSERT(sr->smb_data.chain_offset ==
! (sr->smb2_cmd_hdr + SMB2_HDR_SIZE + 24));
/*
! * This is checked by our callers, but let's make sure.
*/
! ASSERT(sr->fid_ofile->f_node != NULL);
! for (i = 0; i < LockCount; i++) {
! rc = smb_mbc_decodef(
! &sr->smb_data, "qqll",
! &elem.Offset, /* q */
! &elem.Length, /* q */
! &elem.Flags, /* l */
! &elem.reserved); /* l */
! if (rc) {
! status = NT_STATUS_INTERNAL_ERROR;
break;
! }
! status = smb2_lock_elem(sr, &elem);
! if (status)
break;
}
return (status);
}
static uint32_t
! smb2_lock_elem(smb_request_t *sr, struct SMB2_LOCK_ELEMENT *elem)
{
! smb_node_t *node = sr->fid_ofile->f_node;
uint32_t status;
uint32_t ltype;
! uint32_t timeout = 0;
! switch (elem->Flags) {
case SMB2_LOCKFLAG_SHARED_LOCK:
- timeout = UINT_MAX;
- /* FALLTHROUGH */
- case SMB2_LOCKFLAG_SHARED_LOCK | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
ltype = SMB_LOCK_TYPE_READONLY;
- status = smb_lock_range(sr,
- elem->Offset, elem->Length,
- timeout, ltype);
break;
case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
- timeout = UINT_MAX;
- /* FALLTHROUGH */
- case SMB2_LOCKFLAG_EXCLUSIVE_LOCK | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
ltype = SMB_LOCK_TYPE_READWRITE;
- status = smb_lock_range(sr,
- elem->Offset, elem->Length,
- timeout, ltype);
break;
! case SMB2_LOCKFLAG_UNLOCK:
! status = smb_unlock_range(sr, node,
! elem->Offset, elem->Length);
! break;
/*
! * We've already checked the flags previously, so any
! * surprises here are some kind of internal error.
*/
! default:
! status = NT_STATUS_INTERNAL_ERROR;
! break;
}
return (status);
}
--- 86,410 ----
status = NT_STATUS_INSUFFICIENT_RESOURCES;
goto errout;
}
/*
! * Check the LockSequence to determine whether a previous
! * lock request succeeded, but the client disconnected
! * (retaining a durable or resilient handle). If so, this
! * is a lock "replay". We'll find the lock sequence here
! * and return success without processing the lock again.
*/
! if (sr->session->dialect < SMB_VERS_2_1)
! LockSequence = 0;
! if ((sr->session->dialect == SMB_VERS_2_1) &&
! sr->fid_ofile->dh_vers != SMB2_RESILIENT)
! LockSequence = 0;
! /* dialect 3.0 or later can always use LockSequence */
!
! if (LockSequence != 0 &&
! smb2_lock_chk_lockseq(sr->fid_ofile, LockSequence)) {
! status = NT_STATUS_SUCCESS;
goto errout;
}
/*
! * Parse the array of SMB2_LOCK_ELEMENT structs.
! * This array is free'd in smb_srm_fini.
*/
! lvec = smb_srm_zalloc(sr, LockCount * sizeof (*lvec));
! for (i = 0; i < LockCount; i++) {
! lk = &lvec[i];
! rc = smb_mbc_decodef(
! &sr->smb_data, "qqll",
! &lk->Offset, /* q */
! &lk->Length, /* q */
! &lk->Flags, /* l */
! &lk->reserved); /* l */
! if (rc) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
}
/*
! * [MS-SMB2] 3.3.5.14
! * If the flags of the [first element of] the Locks array
! * [has] SMB2_LOCKFLAG_UNLOCK set, the server MUST process
! * the lock array as a series of unlocks. Otherwise, it
! * MUST process the lock array as a series of lock requests.
*/
! sr->arg.lock.lvec = lvec;
! sr->arg.lock.lcnt = LockCount;
! sr->arg.lock.lseq = LockSequence;
! if (lvec[0].Flags & SMB2_LOCKFLAG_UNLOCK) {
! status = smb2_unlock(sr);
! } else {
! status = smb2__lock(sr);
}
! if (sr->fid_ofile->dh_persist) {
! smb2_dh_update_locks(sr, sr->fid_ofile);
! }
+ errout:
+ sr->smb2_status = status;
+ DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
+
+ if (status) {
+ smb2sr_put_error(sr, status);
+ return (SDRC_SUCCESS);
+ }
+
/*
! * Encode SMB2 Lock reply
*/
(void) smb_mbc_encodef(
&sr->reply, "w..",
! 4); /* StructSize w */
/* reserved .. */
return (SDRC_SUCCESS);
}
! /*
! * Process what should be an array of unlock requests.
! */
! static uint32_t
! smb2_unlock(smb_request_t *sr)
{
! lock_elem_t *lk;
! lock_elem_t *lvec = sr->arg.lock.lvec;
! uint32_t LockCount = sr->arg.lock.lcnt;
! uint32_t LockSequence = sr->arg.lock.lseq;
! uint32_t status = 0;
! uint32_t pid = 0; /* SMB2 ignores lock PIDs. */
! int i;
! for (i = 0; i < LockCount; i++) {
! lk = &lvec[i];
! if (lk->Flags != SMB2_LOCKFLAG_UNLOCK) {
! status = NT_STATUS_INVALID_PARAMETER;
! break;
}
! status = smb_unlock_range(sr, lk->Offset, lk->Length, pid);
! if (status != 0)
! break;
! }
! if (status == 0 && LockSequence != 0) {
! smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
! }
! return (status);
}
/*
! * Process what should be an array of lock requests.
*/
static uint32_t
! smb2__lock(smb_request_t *sr)
{
! lock_elem_t *lk;
! lock_elem_t *lvec = sr->arg.lock.lvec;
! uint32_t LockCount = sr->arg.lock.lcnt;
! uint32_t LockSequence = sr->arg.lock.lseq;
! uint32_t i;
! uint32_t ltype;
! uint32_t pid = 0; /* SMB2 ignores lock PIDs */
! uint32_t timeout = 0;
uint32_t status = 0;
! for (i = 0; i < LockCount; i++) {
! lk = &lvec[i];
+ switch (lk->Flags) {
+
+ case SMB2_LOCKFLAG_SHARED_LOCK:
+ case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
/*
! * Blocking locks have special rules:
! * Must be exactly one element, else
! * invalid parameter.
*/
! if (i == 0 && LockCount == 1) {
! status = smb2_lock_blocking(sr);
! return (status);
! }
! /* FALLTHROUGH */
! case SMB2_LOCKFLAG_UNLOCK:
! default:
! status = NT_STATUS_INVALID_PARAMETER;
! goto end_loop;
! /* BEGIN CSTYLED */
! case SMB2_LOCKFLAG_SHARED_LOCK |
! SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
! /* END CSTYLED */
! ltype = SMB_LOCK_TYPE_READONLY;
break;
!
! /* BEGIN CSTYLED */
! case SMB2_LOCKFLAG_EXCLUSIVE_LOCK |
! SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
! /* END CSTYLED */
! ltype = SMB_LOCK_TYPE_READWRITE;
break;
}
+
+ status = smb_lock_range(sr, lk->Offset, lk->Length, pid,
+ ltype, timeout);
+ if (status != 0) {
+ goto end_loop;
+ }
+ }
+
+ end_loop:
+ if (status != 0) {
+ /*
+ * Oh... we have to rollback.
+ */
+ while (i > 0) {
+ --i;
+ lk = &lvec[i];
+ (void) smb_unlock_range(sr,
+ lk->Offset, lk->Length, pid);
+ }
+ }
+ if (status == 0 && LockSequence != 0)
+ smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+
return (status);
}
+ /*
+ * Handler for blocking lock requests, which may "go async".
+ * Always exactly one lock request here.
+ */
static uint32_t
! smb2_lock_blocking(smb_request_t *sr)
{
! lock_elem_t *lk = sr->arg.lock.lvec;
! uint32_t LockCount = sr->arg.lock.lcnt;
! uint32_t LockSequence = sr->arg.lock.lseq;
uint32_t status;
uint32_t ltype;
! uint32_t pid = 0; /* SMB2 ignores lock PIDs */
! uint32_t timeout = UINT_MAX;
! ASSERT(sr->fid_ofile->f_node != NULL);
! ASSERT(LockCount == 1);
!
! switch (lk->Flags) {
case SMB2_LOCKFLAG_SHARED_LOCK:
ltype = SMB_LOCK_TYPE_READONLY;
break;
case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
ltype = SMB_LOCK_TYPE_READWRITE;
break;
! default:
! ASSERT(0);
! return (NT_STATUS_INTERNAL_ERROR);
! }
/*
! * Try the lock first with timeout=0 as we can often
! * get a lock without going async and avoid an extra
! * round trip with the client. Also, only go async
! * for status returns that mean we will block.
*/
! status = smb_lock_range(sr, lk->Offset, lk->Length, pid, ltype, 0);
! if (status == NT_STATUS_LOCK_NOT_GRANTED ||
! status == NT_STATUS_FILE_LOCK_CONFLICT) {
! status = smb2sr_go_async(sr);
! if (status != 0)
! return (status);
! status = smb_lock_range(sr, lk->Offset, lk->Length,
! pid, ltype, timeout);
}
+ if (status == 0 && LockSequence != 0)
+ smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+
return (status);
+ }
+
+ /*
+ * Check whether we've stored a given LockSequence
+ *
+ * [MS-SMB2] 3.3.5.14
+ *
+ * The server verifies the LockSequence by performing the following steps:
+ *
+ * 1. The server MUST use LockSequenceIndex as an index into the
+ * Open.LockSequenceArray in order to locate the sequence number entry.
+ * If the index exceeds the maximum extent of the Open.LockSequenceArray,
+ * or LockSequenceIndex is 0, or if the sequence number entry is empty,
+ * the server MUST skip step 2 and continue lock/unlock processing.
+ *
+ * 2. The server MUST compare LockSequenceNumber to the SequenceNumber of
+ * the entry located in step 1. If the sequence numbers are equal, the
+ * server MUST complete the lock/unlock request with success. Otherwise,
+ * the server MUST reset the entry value to empty and continue lock/unlock
+ * processing.
+ */
+ boolean_t
+ smb2_lock_chk_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+ {
+ uint32_t lsi;
+ uint8_t lsn;
+ boolean_t rv;
+
+ /*
+ * LockSequenceNumber is the low four bits.
+ * LockSequenceIndex is the remaining 28 bits.
+ * valid range is 1..64, which we convert to an
+ * array index in the range 0..63
+ */
+ lsn = lockseq & SMB2_LSN_MASK;
+ lsi = (lockseq >> SMB2_LSN_SHIFT);
+ if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX)
+ return (B_FALSE);
+ --lsi;
+
+ mutex_enter(&ofile->f_mutex);
+
+ if (ofile->f_lock_seq[lsi] == lsn) {
+ rv = B_TRUE;
+ } else {
+ ofile->f_lock_seq[lsi] = (uint8_t)-1; /* "Empty" */
+ rv = B_FALSE;
+ }
+
+ mutex_exit(&ofile->f_mutex);
+
+ return (rv);
+ }
+
+ static void
+ smb2_lock_set_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+ {
+ uint32_t lsi;
+ uint8_t lsn;
+
+ /*
+ * LockSequenceNumber is the low four bits.
+ * LockSequenceIndex is the remaining 28 bits.
+ * valid range is 1..64, which we convert to an
+ * array index in the range 0..63
+ */
+ lsn = lockseq & SMB2_LSN_MASK;
+ lsi = (lockseq >> SMB2_LSN_SHIFT);
+ if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) {
+ cmn_err(CE_NOTE, "smb2_lock_set_lockseq, index=%u", lsi);
+ return;
+ }
+ --lsi;
+
+ mutex_enter(&ofile->f_mutex);
+
+ ofile->f_lock_seq[lsi] = lsn;
+
+ mutex_exit(&ofile->f_mutex);
}