diff options
author | Gordon Ross <gwr@racktopsystems.com> | 2022-02-21 12:10:09 -0500 |
---|---|---|
committer | Toomas Soome <tsoome@me.com> | 2022-12-14 00:03:26 +0200 |
commit | 72b35b0568511bf35ca88532dff910dc0f16847f (patch) | |
tree | d3d5016cf6d9f66dc78cefa1f521f8b74d3f5210 /usr/src/uts/common/fs/smbsrv/smb_oplock.c | |
parent | 479710a24812632178acfabf12b47287101b7575 (diff) | |
download | illumos-joyent-72b35b0568511bf35ca88532dff910dc0f16847f.tar.gz |
15188 smb oplock blocking access to folders
Reviewed by: Albert Lee <alee@racktopsystems.com>
Reviewed by: Jerry Jelinek <gjelinek@racktopsystems.com>
Reviewed by: Joyce McIntosh <jmcintosh@racktopsystems.com>
Reviewed by: Matt Barden <mbarden@racktopsystems.com>
Approved by: Dan McDonald <danmcd@mnx.io>
Diffstat (limited to 'usr/src/uts/common/fs/smbsrv/smb_oplock.c')
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb_oplock.c | 225 |
1 files changed, 209 insertions, 16 deletions
diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c index 5215da8693..a3fd103fa1 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c @@ -21,6 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. + * Copyright 2022 RackTop Systems, Inc. */ /* @@ -32,6 +33,199 @@ #define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE) /* + * This is called by the SMB1 "Locking_andX" handler, + * for SMB1 oplock break acknowledgement. + * This is an "Ack" from the client. + */ +void +smb1_oplock_ack_break(smb_request_t *sr, uchar_t oplock_level) +{ + smb_ofile_t *ofile; + smb_node_t *node; + uint32_t NewLevel; + + ofile = sr->fid_ofile; + node = ofile->f_node; + + if (oplock_level == 0) + NewLevel = OPLOCK_LEVEL_NONE; + else + NewLevel = OPLOCK_LEVEL_TWO; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); + + (void) smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); +} + +/* + * Compose an SMB1 Oplock Break Notification packet, including + * the SMB1 header and everything, in sr->reply. + * The caller will send it and free the request. + */ +static void +smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel) +{ + smb_ofile_t *ofile = sr->fid_ofile; + uint16_t fid; + uint8_t lock_type; + uint8_t oplock_level; + + /* + * Convert internal level to SMB1 + */ + switch (NewLevel) { + default: + ASSERT(0); + /* FALLTHROUGH */ + case OPLOCK_LEVEL_NONE: + oplock_level = 0; + break; + + case OPLOCK_LEVEL_TWO: + oplock_level = 1; + break; + } + + sr->smb_com = SMB_COM_LOCKING_ANDX; + sr->smb_tid = ofile->f_tree->t_tid; + sr->smb_pid = 0xFFFF; + sr->smb_uid = 0; + sr->smb_mid = 0xFFFF; + fid = ofile->f_fid; + lock_type = LOCKING_ANDX_OPLOCK_RELEASE; + + (void) smb_mbc_encodef( + &sr->reply, "Mb19.wwwwbb3.wbb10.", + /* "\xffSMB" M */ + sr->smb_com, /* b */ + /* status, flags, signature 19. */ + sr->smb_tid, /* w */ + sr->smb_pid, /* w */ + sr->smb_uid, /* w */ + sr->smb_mid, /* w */ + 8, /* word count b */ + 0xFF, /* AndX cmd b */ + /* AndX reserved, offset 3. */ + fid, + lock_type, + oplock_level); +} + +/* + * Send an oplock break over the wire, or if we can't, + * then process the oplock break locally. + * + * [MS-CIFS] 3.3.4.2 Object Store Indicates an OpLock Break + * + * This is mostly similar to smb2_oplock_send_break() + * See top comment there about the design. + * Called from smb_oplock_async_break. + * + * This handles only SMB1, which has no durable handles, + * and never has GRANULAR oplocks. + */ +void +smb1_oplock_send_break(smb_request_t *sr) +{ + smb_ofile_t *ofile = sr->fid_ofile; + smb_node_t *node = ofile->f_node; + uint32_t NewLevel = sr->arg.olbrk.NewLevel; + boolean_t AckReq = sr->arg.olbrk.AckRequired; + uint32_t status; + int rc; + + /* + * SMB1 clients should only get Level II oplocks if they + * set the capability indicating they know about them. + */ + if (NewLevel == OPLOCK_LEVEL_TWO && + ofile->f_oplock.og_dialect < NT_LM_0_12) + NewLevel = OPLOCK_LEVEL_NONE; + + /* + * Build the break message in sr->reply. + * It's free'd in smb_request_free(). + * Always SMB1 here. + */ + sr->reply.max_bytes = MLEN; + smb1_oplock_break_notification(sr, NewLevel); + + /* + * Try to send the break message to the client. + * If connected, this IF body will be true. + */ + if (sr->session == ofile->f_session) + rc = smb_session_send(sr->session, 0, &sr->reply); + else + rc = ENOTCONN; + + if (rc != 0) { + /* + * We were unable to send the oplock break request, + * presumably because the connection is gone. + * Just close the handle. + */ + smb_ofile_close(ofile, 0); + return; + } + + /* + * OK, we were able to send the break message. + * If no ack. required, we're done. + */ + if (!AckReq) + return; + + /* + * We're expecting an ACK. Wait in this thread + * so we can log clients that don't respond. + */ + status = smb_oplock_wait_ack(sr); + if (status == 0) + return; + + cmn_err(CE_NOTE, "clnt %s oplock break timeout", + sr->session->ip_addr_str); + DTRACE_PROBE1(ack_timeout, smb_request_t *, sr); + + /* + * Did not get an ACK, so do the ACK locally. + * Note: always break to none here, regardless + * of what the passed in cache level was. + */ + NewLevel = OPLOCK_LEVEL_NONE; + + smb_llist_enter(&node->n_ofile_list, RW_READER); + mutex_enter(&node->n_oplock.ol_mutex); + + ofile->f_oplock.og_breaking = 0; + cv_broadcast(&ofile->f_oplock.og_ack_cv); + + status = smb_oplock_ack_break(sr, ofile, &NewLevel); + + ofile->f_oplock.og_state = NewLevel; + + mutex_exit(&node->n_oplock.ol_mutex); + smb_llist_exit(&node->n_ofile_list); + +#ifdef DEBUG + if (status != 0) { + cmn_err(CE_NOTE, "clnt %s local oplock ack, status=0x%x", + sr->session->ip_addr_str, status); + } +#endif +} + +/* * Client has an open handle and requests an oplock. * Convert SMB1 oplock request info in to internal form, * call common oplock code, convert result to SMB1. @@ -96,7 +290,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } /* - * If exclusive failed (or tree forced shared oplocks) + * If exclusive failed (or the tree forced shared oplocks) * and if the caller supports Level II, try shared. */ if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) { @@ -106,16 +300,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } /* - * Either of the above may have returned the - * status code that says we should wait. - */ - if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { - (void) smb_oplock_wait_break(sr, ofile->f_node, 0); - status = 0; - } - - /* - * Keep track of what we got (in ofile->f_oplock.og_state) + * Keep track of what we got (ofile->f_oplock.og_state etc) * so we'll know what we had when sending a break later. * The og_dialect here is the oplock dialect, which may be * different than SMB dialect. Pre-NT clients did not @@ -123,27 +308,27 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) * client that didn't set the CAP_LEVEL_II_OPLOCKS in * its capabilities, let og_dialect = LANMAN2_1. */ - ofile->f_oplock.og_dialect = (level2ok) ? - NT_LM_0_12 : LANMAN2_1; switch (status) { case NT_STATUS_SUCCESS: + case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS: + ofile->f_oplock.og_dialect = (level2ok) ? + NT_LM_0_12 : LANMAN2_1; ofile->f_oplock.og_state = op->op_oplock_state; + ofile->f_oplock.og_breaking = 0; break; case NT_STATUS_OPLOCK_NOT_GRANTED: - ofile->f_oplock.og_state = 0; op->op_oplock_level = SMB_OPLOCK_NONE; return; default: /* Caller did not check args sufficiently? */ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x", sr->session->ip_addr_str, status); - ofile->f_oplock.og_state = 0; op->op_oplock_level = SMB_OPLOCK_NONE; return; } /* - * Have STATUS_SUCCESS + * Only succes cases get here. * Convert internal oplock state to SMB1 */ if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) { @@ -155,4 +340,12 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok) } else { op->op_oplock_level = SMB_OPLOCK_NONE; } + + /* + * An smb_oplock_reqest call may have returned the + * status code that says we should wait. + */ + if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) { + (void) smb_oplock_wait_break(sr, ofile->f_node, 0); + } } |