[PATCHES] smb3 create replay (for multi-channel)

Michael Adam obnox at samba.org
Wed Mar 2 21:51:43 UTC 2016


On 2016-03-02 at 22:45 +0100, Michael Adam wrote:
> On 2016-03-02 at 13:28 -0800, Jeremy Allison wrote:
> > On Wed, Mar 02, 2016 at 09:54:04PM +0100, Michael Adam wrote:
> > > On 2016-03-02 at 12:05 -0800, Jeremy Allison wrote:
> > > > On Tue, Mar 01, 2016 at 02:15:33AM +0100, Michael Adam wrote:
> > > > > 
> > > > > here is an important step toward multi channel:
> > > > > Implementation of create replay.
> > > > > 
> > > > > Review appreciated.
> > > > 
> > > > LGTM - with the first (already applied) patches
> > > > removed ! Pushed.
> > > 
> > > Thanks a lot Jeremy!
> > 
> > Sure, send me the revised and the follow-up...
> 
> Attached find the updated patchset.
> Differences with the original one:
> 
> - some cosmetic clean-up of commit messages
>   and test names
> - added three new tests:
>   - replay-dht2-oplock-lease
>     (create with oplock, replay with lease)
>   - replay-dht2-lease-oplock
>     (create with lease, replay with oplock)
>   - replay-dht2-lease3
>     (replay with a different lease key)
> - fixed treatment of leases in create replay
>   in corner cases
> 
> Cheers - Michael

In order to make the re-review easier, her
is the diff of the original state rebased on
current master and the revised state.

As you can see, it's mainly the added tests
and one small block in smb2_create.c

Cheers - Michael
-------------- next part --------------
 selftest/knownfail            |   1 +
 source3/include/vfs.h         |   2 +-
 source3/smbd/smb2_create.c    |  26 +++
 source4/torture/smb2/replay.c | 398 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 419 insertions(+), 8 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 9326083..1ac99d4 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -202,6 +202,7 @@
 ^samba3.smb2.setinfo.setinfo
 ^samba3.smb2.session.*reauth5 # some special anonymous checks?
 ^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED)
+^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
 ^samba3.raw.session.*reauth2 # maybe fix this?
diff --git a/source3/include/vfs.h b/source3/include/vfs.h
index 9ea4e75..231eb83 100644
--- a/source3/include/vfs.h
+++ b/source3/include/vfs.h
@@ -254,7 +254,7 @@ typedef struct files_struct {
 	bool write_time_forced;
 
 	int oplock_type;
-	struct fsp_lease *lease; /* Not yet used. Placeholder for leases. */
+	struct fsp_lease *lease;
 	int sent_oplock_break;
 	struct tevent_timer *oplock_timeout;
 	struct lock_struct last_lock_failure;
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index bd64df7..34e07ec 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -953,6 +953,32 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				DEBUG(10, ("v2 lease key only for SMB3\n"));
 				lease_ptr = NULL;
 			}
+
+			/*
+			 * Replay with a lease is only allowed if the
+			 * established open carries a lease with the
+			 * same lease key.
+			 */
+			if (replay_operation) {
+				struct smb2_lease *op_ls =
+						&op->compat->lease->lease;
+				int op_oplock = op->compat->oplock_type;
+
+				if (map_samba_oplock_levels_to_smb2(op_oplock)
+				    != SMB2_OPLOCK_LEVEL_LEASE)
+				{
+					status = NT_STATUS_ACCESS_DENIED;
+					(void)tevent_req_nterror(req, status);
+					return tevent_req_post(req, ev);
+				}
+				if (!smb2_lease_key_equal(&lease.lease_key,
+							  &op_ls->lease_key))
+				{
+					status = NT_STATUS_ACCESS_DENIED;
+					(void)tevent_req_nterror(req, status);
+					return tevent_req_post(req, ev);
+				}
+			}
 		}
 
 		/* these are ignored for SMB2 */
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index b26de3a..c32533b 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -759,6 +759,110 @@ done:
 }
 
 /**
+ * Test Durablity V2 Create Replay Detection on Single Channel.
+ * Create with an oplock, and replay with a lease.
+ */
+static bool test_replay_dhv2_oplock_lease(struct torture_context *tctx,
+					  struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h;
+	struct smb2_handle *h = NULL;
+	struct smb2_create io;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay_dhv2_oplock1.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 on Single "
+			      "Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
+	CHECK_VAL(break_info.count, 0);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io.out.durable_open, false);
+	if (share_is_so) {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s"));
+		CHECK_VAL(io.out.durable_open_v2, false);
+		CHECK_VAL(io.out.timeout, 0);
+	} else {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+		CHECK_VAL(io.out.durable_open_v2, true);
+		CHECK_VAL(io.out.timeout, io.in.timeout);
+	}
+
+	/*
+	 * Replay Durable V2 Create on single channel
+	 * but replay it with a lease instead of an oplock.
+	 */
+	lease_key = random();
+	smb2_lease_create(&io, &ls, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
+
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+
+/**
  * Test durablity v2 create replay detection on single channel.
  * Variant with leases instead of oplocks:
  * - open a file with a rh lease
@@ -1048,6 +1152,283 @@ done:
 }
 
 /**
+ * Test durablity v2 create replay detection on single channel.
+ * create with a lease, and replay with a different lease key
+ */
+static bool test_replay_dhv2_lease3(struct torture_context *tctx,
+				    struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease2.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * use a different lease key.
+	 */
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			random() /* lease key */,
+			smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
+ * Test durablity v2 create replay detection on single channel.
+ * Do the original create with a lease, and do the replay
+ * with an oplock.
+ */
+static bool test_replay_dhv2_lease_oplock(struct torture_context *tctx,
+					  struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease1.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ref1 = io1;
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * We get the io from open #1 but with the
+	 * upgraded lease.
+	 */
+
+	smb2_oplock_create_share(&io2, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = create_guid;
+	io2.in.timeout = UINT32_MAX;
+
+	/* adapt expected lease in response */
+	if (!share_is_so) {
+		ref1.out.lease_response.lease_state =
+			smb2_util_lease_state("RHW");
+	}
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATE_OUT(&io1, &ref1);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
  * Test Durablity V2 Create Replay Detection on Multi Channel
  */
 static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1)
@@ -1580,13 +1961,16 @@ struct torture_suite *torture_smb2_replay_init(void)
 	struct torture_suite *suite =
 		torture_suite_create(talloc_autofree_context(), "replay");
 
-	torture_suite_add_1smb2_test(suite, "replay_commands", test_replay_commands);
-	torture_suite_add_1smb2_test(suite, "replay_regular", test_replay_regular);
-	torture_suite_add_1smb2_test(suite, "replay_dhv2_oplock1", test_replay_dhv2_oplock1);
-	torture_suite_add_1smb2_test(suite, "replay_dhv2_oplock2", test_replay_dhv2_oplock2);
-	torture_suite_add_1smb2_test(suite, "replay_dhv2_oplock3", test_replay_dhv2_oplock3);
-	torture_suite_add_1smb2_test(suite, "replay_dhv2_lease1",  test_replay_dhv2_lease1);
-	torture_suite_add_1smb2_test(suite, "replay_dhv2_lease2",  test_replay_dhv2_lease2);
+	torture_suite_add_1smb2_test(suite, "replay-commands", test_replay_commands);
+	torture_suite_add_1smb2_test(suite, "replay-regular", test_replay_regular);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock1", test_replay_dhv2_oplock1);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock-lease", test_replay_dhv2_oplock_lease);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2",  test_replay_dhv2_lease2);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease3",  test_replay_dhv2_lease3);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease-oplock",  test_replay_dhv2_lease_oplock);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
 	torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20160302/7dfb8f18/signature-0001.sig>


More information about the samba-technical mailing list