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,52 +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.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Dispatch function for SMB2_LOCK
*/
#include <smbsrv/smb2_kproto.h>
-struct SMB2_LOCK_ELEMENT {
+/*
+ * [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 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 *);
+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)
{
- struct SMB2_LOCK_ELEMENT elem;
+ lock_elem_t *lvec, *lk;
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;
+ int rc;
/*
- * SMB2 Lock request
+ * Decode SMB2 Lock request
*/
rc = smb_mbc_decodef(
&sr->smb_data, "wwlqq",
&StructSize, /* w */
&LockCount, /* w */
@@ -61,13 +68,18 @@
&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;
+ 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,220 +86,325 @@
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.
+ * 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.
*/
- 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;
+ 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;
}
/*
- * Make sure the flags are valid;
- * Find out if we might block.
+ * Parse the array of SMB2_LOCK_ELEMENT structs.
+ * This array is free'd in smb_srm_fini.
*/
- 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:
+ 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;
}
}
- if (MayBlock) {
/*
- * May need to block. "Go async".
+ * [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.
*/
- status = smb2sr_go_async(sr, smb2_lock_async);
- goto errout;
+ 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);
}
- sr->smb_data.chain_offset = save_offset;
- status = smb2_lock_exec(sr, LockCount);
- if (status)
- goto errout;
+ 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);
+ }
+
/*
- * SMB2 Lock reply (sync)
+ * Encode SMB2 Lock reply
*/
- StructSize = 4;
(void) smb_mbc_encodef(
&sr->reply, "w..",
- StructSize); /* w */
+ 4); /* 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)
+/*
+ * Process what should be an array of unlock requests.
+ */
+static uint32_t
+smb2_unlock(smb_request_t *sr)
{
- smb2fid_t smb2fid;
- uint32_t LockSequence;
- uint32_t status;
- uint16_t StructSize;
- uint16_t LockCount;
- int rc = 0;
+ 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;
- /*
- * 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);
+ for (i = 0; i < LockCount; i++) {
+ lk = &lvec[i];
- 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;
+ if (lk->Flags != SMB2_LOCKFLAG_UNLOCK) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
}
- status = smb2_lock_exec(sr, LockCount);
- if (status)
- goto errout;
+ 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);
+ }
- /*
- * 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);
+ return (status);
}
/*
- * 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.
+ * Process what should be an array of lock requests.
*/
static uint32_t
-smb2_lock_exec(smb_request_t *sr, uint16_t LockCount)
+smb2__lock(smb_request_t *sr)
{
- struct SMB2_LOCK_ELEMENT elem;
+ 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;
- 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));
+ for (i = 0; i < LockCount; i++) {
+ lk = &lvec[i];
+ switch (lk->Flags) {
+
+ case SMB2_LOCKFLAG_SHARED_LOCK:
+ case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
/*
- * This is checked by our callers, but let's make sure.
+ * Blocking locks have special rules:
+ * Must be exactly one element, else
+ * invalid parameter.
*/
- ASSERT(sr->fid_ofile->f_node != NULL);
+ 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;
- 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;
+ /* BEGIN CSTYLED */
+ case SMB2_LOCKFLAG_SHARED_LOCK |
+ SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
+ /* END CSTYLED */
+ ltype = SMB_LOCK_TYPE_READONLY;
break;
- }
- status = smb2_lock_elem(sr, &elem);
- if (status)
+
+ /* 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_elem(smb_request_t *sr, struct SMB2_LOCK_ELEMENT *elem)
+smb2_lock_blocking(smb_request_t *sr)
{
- smb_node_t *node = sr->fid_ofile->f_node;
+ 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 timeout = 0;
+ uint32_t pid = 0; /* SMB2 ignores lock PIDs */
+ uint32_t timeout = UINT_MAX;
- switch (elem->Flags) {
+ ASSERT(sr->fid_ofile->f_node != NULL);
+ ASSERT(LockCount == 1);
+
+ switch (lk->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;
+ default:
+ ASSERT(0);
+ return (NT_STATUS_INTERNAL_ERROR);
+ }
/*
- * We've already checked the flags previously, so any
- * surprises here are some kind of 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.
*/
- default:
- status = NT_STATUS_INTERNAL_ERROR;
- break;
+ 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);
}