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); }