summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/smbsrv/smb_oplock.c
diff options
context:
space:
mode:
authorGordon Ross <gwr@racktopsystems.com>2022-02-21 12:10:09 -0500
committerToomas Soome <tsoome@me.com>2022-12-14 00:03:26 +0200
commit72b35b0568511bf35ca88532dff910dc0f16847f (patch)
treed3d5016cf6d9f66dc78cefa1f521f8b74d3f5210 /usr/src/uts/common/fs/smbsrv/smb_oplock.c
parent479710a24812632178acfabf12b47287101b7575 (diff)
downloadillumos-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.c225
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);
+ }
}