[PATCHES] implement smb3 multi-channel write (etc) replay

Michael Adam obnox at samba.org
Tue Mar 15 11:52:25 UTC 2016


Hi,

attached find the next polished round of multi-channel patches.
These implement the treatment of the replay operation flag in
file-id based calls, and the outstanding request counters.

Also corresponding additional tests.
Note: These tests do not get fully triggered yet in selftest
since the client code will only behave accordingly  when the
server announces multi-channel capability in the negotiate
response. We have not enabled that in selftest yet -- will follow
soon.

Apart from the enablement of multi-channel in self-test, this
patchset leaves the treatment of oplock break retries
by the server as the one remaining piece. This will follow soon.

Review appreciated!

Michael
-------------- next part --------------
From 308f83492a3ed112f884a3f7b6efb0d6dbd2c550 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Deschner?= <gd at samba.org>
Date: Wed, 27 Jan 2016 16:18:25 +0100
Subject: [PATCH 01/10] s3:smbXsrv.idl: add 8 byte channel_sequence number and
 request counters to IDL.

Guenther

Signed-off-by: Guenther Deschner <gd at samba.org>
---
 source3/librpc/idl/smbXsrv.idl | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/source3/librpc/idl/smbXsrv.idl b/source3/librpc/idl/smbXsrv.idl
index 4c6895a..1bfa51e 100644
--- a/source3/librpc/idl/smbXsrv.idl
+++ b/source3/librpc/idl/smbXsrv.idl
@@ -430,6 +430,7 @@ interface smbXsrv
 		uint32					durable_timeout_msec;
 		boolean8				durable;
 		DATA_BLOB				backend_cookie;
+		hyper					channel_sequence;
 	} smbXsrv_open_global0;
 
 	typedef union {
@@ -470,6 +471,8 @@ interface smbXsrv
 		[ignore] files_struct			*compat;
 		smbXsrv_open_flags			flags;
 		uint32					create_action;
+		hyper					request_count;
+		hyper					pre_request_count;
 	} smbXsrv_open;
 
 	typedef union {
-- 
2.5.0


From 5f1208aac24e7f376be2b513ce2ded5fa10caa72 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 24 Feb 2016 15:51:14 +0100
Subject: [PATCH 02/10] smbd:smb2: add a modify flag to dispatch table

This indicates that an operation is a modifying operation.
Some parts of the upcoming channel sequence number logic
only applies to modify operations.

Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>

Signed-off-by: Michael Adam <obnox at samba.org>
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/smbd/smb2_server.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index 68d637e..7b6d8d6 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -46,6 +46,7 @@ static const struct smbd_smb2_dispatch_table {
 	bool as_root;
 	uint16_t fileid_ofs;
 	bool allow_invalid_fileid;
+	bool modify;
 } smbd_smb2_table[] = {
 #define _OP(o) .opcode = o, .name = #o
 	{
@@ -98,6 +99,7 @@ static const struct smbd_smb2_dispatch_table {
 		.need_session = true,
 		.need_tcon = true,
 		.fileid_ofs = 0x10,
+		.modify = true,
 	},{
 		_OP(SMB2_OP_LOCK),
 		.need_session = true,
@@ -109,6 +111,7 @@ static const struct smbd_smb2_dispatch_table {
 		.need_tcon = true,
 		.fileid_ofs = 0x08,
 		.allow_invalid_fileid = true,
+		.modify = true,
 	},{
 		_OP(SMB2_OP_CANCEL),
 		.as_root = true,
@@ -135,6 +138,7 @@ static const struct smbd_smb2_dispatch_table {
 		.need_session = true,
 		.need_tcon = true,
 		.fileid_ofs = 0x10,
+		.modify = true,
 	},{
 		_OP(SMB2_OP_BREAK),
 		.need_session = true,
-- 
2.5.0


From 4a7e7866f49da5f9681b2e20b0f7a0f1d3a5cc84 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Tue, 15 Mar 2016 12:36:59 +0100
Subject: [PATCH 03/10] smbd:smb2: add request_counters_updated to the
 smbd_smb2_request struct

This will be used to keep track of whether the outstanding request
counters have been updated in the dispatch, so that the reply
code can act accordingly.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/smbd/globals.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
index c843f5a..4c02083 100644
--- a/source3/smbd/globals.h
+++ b/source3/smbd/globals.h
@@ -720,6 +720,13 @@ struct smbd_smb2_request {
 	struct files_struct *compat_chain_fsp;
 
 	/*
+	 * Keep track of whether the outstanding request counters
+	 * had been updated in dispatch, so that they need to be
+	 * adapted again in reply.
+	 */
+	bool request_counters_updated;
+
+	/*
 	 * The sub request for async backend calls.
 	 * This is used for SMB2 Cancel.
 	 */
-- 
2.5.0


From 123ec1da438d3b1fa42114116ce1c17a40c72b28 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 24 Feb 2016 15:54:41 +0100
Subject: [PATCH 04/10] smbd:smb2: implement channel sequence checks and
 request counters in dispatch

Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
Pair-Programmed-With: Guenther Deschner <gd at samba.org>

Signed-off-by: Michael Adam <obnox at samba.org>
Signed-off-by: Stefan Metzmacher <metze at samba.org>
Signed-off-by: Guenther Deschner <gd at samba.org>
---
 source3/smbd/smb2_server.c | 120 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index 7b6d8d6..01892e1 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2111,6 +2111,121 @@ bool smbXsrv_is_partially_signed(uint8_t signing_flags)
 		(signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
 }
 
+static NTSTATUS smbd_smb2_request_dispatch_update_counts(
+				struct smbd_smb2_request *req,
+				bool modify_call)
+{
+	struct smbXsrv_connection *xconn = req->xconn;
+	const uint8_t *inhdr;
+	uint16_t channel_sequence;
+	uint32_t flags;
+	int cmp;
+	struct smbXsrv_open *op;
+	bool update_open = false;
+	NTSTATUS status = NT_STATUS_OK;
+
+	req->request_counters_updated = false;
+
+	if (xconn->protocol < PROTOCOL_SMB2_22) {
+		return NT_STATUS_OK;
+	}
+
+	if (req->compat_chain_fsp == NULL) {
+		return NT_STATUS_OK;
+	}
+
+	op = req->compat_chain_fsp->op;
+	if (op == NULL) {
+		return NT_STATUS_OK;
+	}
+
+	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+	flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+	channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+	cmp = channel_sequence - op->global->channel_sequence;
+
+	if (abs(cmp) > INT16_MAX) {
+		/*
+		 * [MS-SMB2] 3.3.5.2.10 - Verifying the Channel Sequence Number:
+		 *
+		 * If the channel sequence number of the request and the one
+		 * known to the server are not equal, the channel sequence
+		 * number and outstanding request counts are only updated
+		 * "... if the unsigned difference using 16-bit arithmetic
+		 * between ChannelSequence and Open.ChannelSequence is less than
+		 * or equal to 0x7FFF ...".
+		 * Otherwise, an error is returned for the modifying
+		 * calls write, set_info, and ioctl.
+		 *
+		 * There are currently two issues with the description:
+		 *
+		 * * For the other calls, the document seems to imply
+		 *   that processing continues without adapting the
+		 *   counters (if the sequence numbers are not equal).
+		 *
+		 *   TODO: This needs clarification!
+		 *
+		 * * Also, the behaviour if the difference is larger
+		 *   than 0x7FFF is not clear. The document seems to
+		 *   imply that if such a difference is reached,
+		 *   the server starts to ignore the counters or
+		 *   in the case of the modifying calls, return errors.
+		 *
+		 *   TODO: This needs clarification!
+		 *
+		 * At this point Samba tries to be a little more
+		 * clever than the description in the MS-SMB2 document
+		 * by heuristically detecting and properly treating
+		 * a 16 bit overflow of the client-submitted sequence
+		 * number:
+		 *
+		 * If the stored channel squence number is more than
+		 * 0x7FFF larger than the one from the request, then
+		 * the client-provided sequence number has likely
+		 * overflown. We treat this case as valid instead
+		 * of as failure.
+		 *
+		 * The MS-SMB2 behaviour would be setting cmp = -1.
+		 */
+		cmp *= -1;
+	}
+
+	if (!(flags & SMB2_HDR_FLAG_REPLAY_OPERATION)) {
+		if (cmp == 0) {
+			op->request_count += 1;
+			req->request_counters_updated = true;
+		} else if (cmp > 0) {
+			op->pre_request_count += op->request_count;
+			op->request_count = 1;
+			op->global->channel_sequence = channel_sequence;
+			update_open = true;
+			req->request_counters_updated = true;
+		} else if (modify_call) {
+			return NT_STATUS_FILE_NOT_AVAILABLE;
+		}
+	} else {
+		if (cmp == 0 && op->pre_request_count == 0) {
+			op->request_count += 1;
+			req->request_counters_updated = true;
+		} else if (cmp > 0 && op->pre_request_count == 0) {
+			op->pre_request_count += op->request_count;
+			op->request_count = 1;
+			op->global->channel_sequence = channel_sequence;
+			update_open = true;
+			req->request_counters_updated = true;
+		} else if (modify_call) {
+			return NT_STATUS_FILE_NOT_AVAILABLE;
+		}
+	}
+
+	if (update_open) {
+		status = smbXsrv_open_update(op);
+	}
+
+	return status;
+}
+
 NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 {
 	struct smbXsrv_connection *xconn = req->xconn;
@@ -2408,6 +2523,11 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 		}
 	}
 
+	status = smbd_smb2_request_dispatch_update_counts(req, call->modify);
+	if (!NT_STATUS_IS_OK(status)) {
+		return smbd_smb2_request_error(req, status);
+	}
+
 	if (call->as_root) {
 		SMB_ASSERT(call->fileid_ofs == 0);
 		/* This call needs to be run as root */
-- 
2.5.0


From 5fdf3082ab00c034f70ebc089353af5c9e18613c Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Tue, 23 Feb 2016 20:54:34 +0100
Subject: [PATCH 05/10] smbd:smb2: update outstanding request counters before
 sending a reply

This is part of the channel sequence number treatment of multi-channel.

Pair-Programmed-With: Guenther Deschner <gd at samba.org>

Signed-off-by: Michael Adam <obnox at samba.org>
Signed-off-by: Guenther Deschner <gd at samba.org>
---
 source3/smbd/smb2_server.c | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index 01892e1..ae125df 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2676,6 +2676,40 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 	return return_value;
 }
 
+static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
+{
+	struct smbXsrv_connection *xconn = req->xconn;
+	const uint8_t *inhdr;
+	uint16_t channel_sequence;
+	struct smbXsrv_open *op;
+
+	if (!req->request_counters_updated) {
+		return;
+	}
+
+	if (xconn->protocol < PROTOCOL_SMB2_22) {
+		return;
+	}
+
+	if (req->compat_chain_fsp == NULL) {
+		return;
+	}
+
+	op = req->compat_chain_fsp->op;
+	if (op == NULL) {
+		return;
+	}
+
+	inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+	channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+	if (op->global->channel_sequence == channel_sequence) {
+		op->request_count -= 1;
+	} else {
+		op->pre_request_count -= 1;
+	}
+}
+
 static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
 {
 	struct smbXsrv_connection *xconn = req->xconn;
@@ -2689,6 +2723,9 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
 	req->subreq = NULL;
 	TALLOC_FREE(req->async_te);
 
+	/* MS-SMB2: 3.3.4.1 Sending Any Outgoing Message */
+	smbd_smb2_request_reply_update_counts(req);
+
 	if (req->do_encryption &&
 	    (firsttf->iov_len == 0) &&
 	    (req->first_key.length == 0) &&
-- 
2.5.0


From 3b3907977e03e20ccc64e8b2dfa41b9c5f467222 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Sat, 27 Feb 2016 14:02:02 +0100
Subject: [PATCH 06/10] smbd:smb2: add some asserts before decrementing the
 counters

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/smbd/smb2_server.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index ae125df..8a4aa96 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2704,8 +2704,10 @@ static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
 	channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
 
 	if (op->global->channel_sequence == channel_sequence) {
+		SMB_ASSERT(op->request_count > 0);
 		op->request_count -= 1;
 	} else {
+		SMB_ASSERT(op->pre_request_count > 0);
 		op->pre_request_count -= 1;
 	}
 }
-- 
2.5.0


From 9d958d51308f20c4d7d76452ec3628c51354df04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Deschner?= <gd at samba.org>
Date: Tue, 1 Mar 2016 15:15:10 +0100
Subject: [PATCH 07/10] libcli:smb:smbXcli_base: add
 smb2cli_session_current_channel_sequence() call.

Guenther

Signed-off-by: Guenther Deschner <gd at samba.org>
Reviewed-by: Michael Adam <obnox at samba.org>
---
 libcli/smb/smbXcli_base.c | 5 +++++
 libcli/smb/smbXcli_base.h | 1 +
 2 files changed, 6 insertions(+)

diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c
index ad6a254..48388b6 100644
--- a/libcli/smb/smbXcli_base.c
+++ b/libcli/smb/smbXcli_base.c
@@ -5486,6 +5486,11 @@ uint16_t smb2cli_session_reset_channel_sequence(struct smbXcli_session *session,
 	return prev_cs;
 }
 
+uint16_t smb2cli_session_current_channel_sequence(struct smbXcli_session *session)
+{
+	return session->smb2->channel_sequence;
+}
+
 void smb2cli_session_start_replay(struct smbXcli_session *session)
 {
 	session->smb2->replay_active = true;
diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h
index e4cfb10..ffccd7e 100644
--- a/libcli/smb/smbXcli_base.h
+++ b/libcli/smb/smbXcli_base.h
@@ -410,6 +410,7 @@ void smb2cli_session_set_id_and_flags(struct smbXcli_session *session,
 void smb2cli_session_increment_channel_sequence(struct smbXcli_session *session);
 uint16_t smb2cli_session_reset_channel_sequence(struct smbXcli_session *session,
 						uint16_t channel_sequence);
+uint16_t smb2cli_session_current_channel_sequence(struct smbXcli_session *session);
 void smb2cli_session_start_replay(struct smbXcli_session *session);
 void smb2cli_session_stop_replay(struct smbXcli_session *session);
 NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session,
-- 
2.5.0


From fe3c484d00cd95664fb0ee008e285bf0fc97c36a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Deschner?= <gd at samba.org>
Date: Thu, 25 Feb 2016 11:15:06 +0100
Subject: [PATCH 08/10] torture:smb2: add test for checking sequence number
 wrap around.

Guenther

Signed-off-by: Guenther Deschner <gd at samba.org>
Reviewed-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 266 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 267 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index c15d263..0950ef0 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -207,6 +207,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.channel-sequence
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index c32533b..8388837 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -1428,6 +1428,271 @@ done:
 	return ret;
 }
 
+static bool test_channel_sequence_table(struct torture_context *tctx,
+					struct smb2_tree *tree,
+					bool do_replay,
+					uint16_t opcode)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle handle;
+	struct smb2_handle *phandle = NULL;
+	struct smb2_create io;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\channel_sequence.dat";
+	uint16_t csn = 0;
+	uint16_t limit = UINT16_MAX - 0x7fff;
+	int i;
+	struct {
+		uint16_t csn;
+		bool csn_rand_low;
+		bool csn_rand_high;
+		NTSTATUS expected_status;
+	} tests[] = {
+		{
+			.csn			= 0,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0x7fff + 1,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 0x7fff + 2,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= -1,
+			.csn_rand_high		= true,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 0xffff,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 0x7fff,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0x7ffe,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 0,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= -1,
+			.csn_rand_low		= true,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 0x7fff + 1,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0xffff,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 1,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		},{
+			.csn			= 1,
+			.expected_status	= NT_STATUS_OK,
+		},{
+			.csn			= 0xffff,
+			.expected_status	= NT_STATUS_FILE_NOT_AVAILABLE,
+		}
+	};
+
+	smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 0);
+
+	csn = smb2cli_session_current_channel_sequence(tree->session->smbXcli);
+	torture_comment(tctx, "Testing create with channel sequence number: 0x%04x\n", csn);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access("RWD"),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	torture_assert_ntstatus_ok_goto(tctx,
+		smb2_create(tree, mem_ctx, &io),
+		ret, done, "failed to call smb2_create");
+
+	handle = io.out.file.handle;
+	phandle = &handle;
+
+	for (i=0; i <ARRAY_SIZE(tests); i++) {
+
+		const char *opstr = "";
+		union smb_fileinfo qfinfo;
+
+		csn = tests[i].csn;
+
+		if (tests[i].csn_rand_low) {
+			csn = rand() % limit;
+		} else if (tests[i].csn_rand_high) {
+			csn = rand() % limit + 0x7fff;
+		}
+
+		switch (opcode) {
+		case SMB2_OP_WRITE:
+			opstr = "write";
+			break;
+		case SMB2_OP_IOCTL:
+			opstr = "ioctl";
+			break;
+		case SMB2_OP_SETINFO:
+			opstr = "setinfo";
+			break;
+		default:
+			break;
+		}
+
+		smb2cli_session_reset_channel_sequence(tree->session->smbXcli, csn);
+		csn = smb2cli_session_current_channel_sequence(tree->session->smbXcli);
+
+		torture_comment(tctx, "Testing %s (replay: %s) with CSN 0x%04x, expecting: %s\n",
+			opstr, do_replay ? "true" : "false", csn,
+			nt_errstr(tests[i].expected_status));
+
+		if (do_replay) {
+			smb2cli_session_start_replay(tree->session->smbXcli);
+		}
+
+		switch (opcode) {
+		case SMB2_OP_WRITE: {
+			DATA_BLOB blob = data_blob_talloc(tctx, NULL, 255);
+
+			generate_random_buffer(blob.data, blob.length);
+
+			status = smb2_util_write(tree, handle, blob.data, 0, blob.length);
+			if (NT_STATUS_IS_OK(status)) {
+				struct smb2_read rd;
+
+				rd = (struct smb2_read) {
+					.in.file.handle = handle,
+					.in.length = blob.length,
+					.in.offset = 0
+				};
+
+				torture_assert_ntstatus_ok_goto(tctx,
+					smb2_read(tree, tree, &rd),
+					ret, done, "failed to read after write");
+
+				torture_assert_data_blob_equal(tctx,
+					rd.out.data, blob,
+					"read/write mismatch");
+			}
+			break;
+		}
+		case SMB2_OP_IOCTL: {
+			union smb_ioctl ioctl;
+			ioctl = (union smb_ioctl) {
+				.smb2.level = RAW_IOCTL_SMB2,
+				.smb2.in.file.handle = handle,
+				.smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID,
+				.smb2.in.max_response_size = 64,
+				.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL
+			};
+			status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2);
+			break;
+		}
+		case SMB2_OP_SETINFO: {
+			union smb_setfileinfo sfinfo;
+			ZERO_STRUCT(sfinfo);
+			sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
+			sfinfo.generic.in.file.handle = handle;
+			sfinfo.position_information.in.position = 0x1000;
+			status = smb2_setinfo_file(tree, &sfinfo);
+			break;
+		}
+		default:
+			break;
+		}
+
+		qfinfo = (union smb_fileinfo) {
+			.generic.level = RAW_SFILEINFO_POSITION_INFORMATION,
+			.generic.in.file.handle = handle
+		};
+
+		torture_assert_ntstatus_ok_goto(tctx,
+			smb2_getinfo_file(tree, mem_ctx, &qfinfo),
+			ret, done, "failed to read after write");
+
+		if (do_replay) {
+			smb2cli_session_stop_replay(tree->session->smbXcli);
+		}
+
+		torture_assert_ntstatus_equal_goto(tctx,
+			status, tests[i].expected_status,
+			ret, done, "got unexpected failure code");
+
+	}
+done:
+	if (phandle != NULL) {
+		smb2_util_close(tree, *phandle);
+	}
+
+	smb2_util_unlink(tree, fname);
+
+	return ret;
+}
+
+static bool test_channel_sequence(struct torture_context *tctx,
+				  struct smb2_tree *tree)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	bool ret = true;
+	const char *fname = BASEDIR "\\channel_sequence.dat";
+	struct smb2_transport *transport1 = tree->session->transport;
+	struct smb2_handle handle;
+	uint32_t server_capabilities;
+	uint16_t opcodes[] = { SMB2_OP_WRITE, SMB2_OP_IOCTL, SMB2_OP_SETINFO };
+	int i;
+
+	if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "Replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(
+					tree->session->transport->conn);
+	if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) {
+		torture_skip(tctx,
+			     "Server does not support multi-channel.");
+	}
+
+	torture_comment(tctx, "Testing channel sequence numbers\n");
+
+	torture_assert_ntstatus_ok_goto(tctx,
+		torture_smb2_testdir(tree, BASEDIR, &handle),
+		ret, done, "failed to setup test directory");
+
+	smb2_util_close(tree, handle);
+	smb2_util_unlink(tree, fname);
+
+	for (i=0; i <ARRAY_SIZE(opcodes); i++) {
+		torture_assert(tctx,
+			test_channel_sequence_table(tctx, tree, false, opcodes[i]),
+			"failed to test CSN without replay flag");
+		torture_assert(tctx,
+			test_channel_sequence_table(tctx, tree, true, opcodes[i]),
+			"failed to test CSN with replay flag");
+	}
+
+done:
+
+	smb2_util_unlink(tree, fname);
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
 /**
  * Test Durablity V2 Create Replay Detection on Multi Channel
  */
@@ -1971,6 +2236,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	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, "channel-sequence", test_channel_sequence);
 	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);
-- 
2.5.0


From 97bbdaf33ff4df87b96c57a7ba201dd4428b8264 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Deschner?= <gd at samba.org>
Date: Wed, 24 Feb 2016 19:23:21 +0100
Subject: [PATCH 09/10] lib/torture: add torture_assert_u64_not_equal_goto
 macro
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Guenther

Signed-off-by: Günther Deschner <gd at samba.org>
---
 lib/torture/torture.h | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lib/torture/torture.h b/lib/torture/torture.h
index 356922a..e710873 100644
--- a/lib/torture/torture.h
+++ b/lib/torture/torture.h
@@ -479,6 +479,18 @@ void torture_result(struct torture_context *test,
 	} \
 	} while(0)
 
+#define torture_assert_u64_not_equal_goto(torture_ctx,got,not_expected,ret,label,cmt)\
+	do { uint64_t __got = (got), __not_expected = (not_expected); \
+	if (__got == __not_expected) { \
+		torture_result(torture_ctx, TORTURE_FAIL, \
+			__location__": "#got" was %llu (0x%llX), expected a different number: %s", \
+			(unsigned long long)__got, (unsigned long long)__got, \
+			cmt); \
+		ret = false; \
+		goto label; \
+	} \
+	} while(0)
+
 #define torture_assert_errno_equal(torture_ctx,expected,cmt)\
 	do { int __expected = (expected); \
 	if (errno != __expected) { \
-- 
2.5.0


From 99d648c56d73ef5d19a6595fb5293451a2efc4c7 Mon Sep 17 00:00:00 2001
From: Anubhav Rakshit <anubhav.rakshit at gmail.com>
Date: Thu, 30 Oct 2014 13:20:57 +0530
Subject: [PATCH 10/10] torture:smb2: Add test replay6 to verify Error Codes
 for DurableHandleReqV2 replay

Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>
Pair-Programmed-With: Guenther Deschner <gd at samba.org>
Pair-Programmed-With: Michael Adam <obnox at samba.org>

Signed-off-by: Anubhav Rakshit <anubhav.rakshit at gmail.com>
Signed-off-by: Stefan Metzmacher <metze at samba.org>
Signed-off-by: Guenther Deschner <gd at samba.org>
Signed-off-by: Michael Adam <obnox at samba.org>
---
 source4/torture/smb2/replay.c | 211 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 209 insertions(+), 2 deletions(-)

diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 8388837..91bb568 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -94,7 +94,7 @@
 
 #define BASEDIR "replaytestdir"
 
-static struct {
+struct break_info {
 	struct torture_context *tctx;
 	struct smb2_handle handle;
 	uint8_t level;
@@ -102,7 +102,16 @@ static struct {
 	int count;
 	int failures;
 	NTSTATUS failure_status;
-} break_info;
+};
+
+static struct break_info break_info;
+
+static void torture_reset_break_info(struct torture_context *tctx,
+				     struct break_info *r)
+{
+	ZERO_STRUCTP(r);
+	r->tctx = tctx;
+}
 
 static void torture_oplock_ack_callback(struct smb2_request *req)
 {
@@ -163,6 +172,61 @@ static bool torture_oplock_ack_handler(struct smb2_transport *transport,
 }
 
 /**
+ * Timer handler function notifies the registering function that time is up
+ */
+static void timeout_cb(struct tevent_context *ev,
+		       struct tevent_timer *te,
+		       struct timeval current_time,
+		       void *private_data)
+{
+	bool *timesup = (bool *)private_data;
+	*timesup = true;
+	return;
+}
+
+/**
+ *  Wait a short period of time to receive a single oplock break request
+ */
+static void torture_wait_for_oplock_break(struct torture_context *tctx)
+{
+	TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+	struct tevent_timer *te = NULL;
+	struct timeval ne;
+	bool timesup = false;
+	int old_count = break_info.count;
+
+	/* Wait .1 seconds for an oplock break */
+	ne = tevent_timeval_current_ofs(0, 100000);
+
+	te = tevent_add_timer(tctx->ev, tmp_ctx, ne, timeout_cb, &timesup);
+	if (te == NULL) {
+		torture_comment(tctx, "Failed to wait for an oplock break. "
+				      "test results may not be accurate.");
+		goto done;
+	}
+
+	while (!timesup && break_info.count < old_count + 1) {
+		if (tevent_loop_once(tctx->ev) != 0) {
+			torture_comment(tctx, "Failed to wait for an oplock "
+					      "break. test results may not be "
+					      "accurate.");
+			goto done;
+		}
+	}
+
+done:
+	/*
+	 * We don't know if the timed event fired and was freed, we received
+	 * our oplock break, or some other event triggered the loop.  Thus,
+	 * we create a tmp_ctx to be able to safely free/remove the timed
+	 * event in all 3 cases.
+	 */
+	talloc_free(tmp_ctx);
+
+	return;
+}
+
+/**
  * Test what happens when SMB2_FLAGS_REPLAY_OPERATION is enabled for various
  * commands. We want to verify if the server returns an error code or not.
  */
@@ -2221,6 +2285,148 @@ done:
 	return ret;
 }
 
+
+/**
+ * Test Error Codes when a DurableHandleReqV2 with matching CreateGuid is
+ * re-sent with or without SMB2_FLAGS_REPLAY_OPERATION
+ */
+static bool test_replay6(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, ref1;
+	union smb_fileinfo qfinfo;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay6.dat";
+	struct smb2_transport *transport = tree->session->transport;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	torture_reset_break_info(tctx, &break_info);
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Error Codes for DurableHandleReqV2 Replay\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
+	torture_wait_for_oplock_break(tctx);
+	CHECK_VAL(break_info.count, 0);
+	torture_reset_break_info(tctx, &break_info);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access("RWD"),
+			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);
+	ref1 = io;
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+	CHECK_VAL(io.out.durable_open, false);
+	CHECK_VAL(io.out.durable_open_v2, true);
+
+	io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+	io.in.create_disposition = NTCREATEX_DISP_OPEN;
+	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_OK);
+	CHECK_CREATE_OUT(&io, &ref1);
+	torture_wait_for_oplock_break(tctx);
+	CHECK_VAL(break_info.count, 0);
+	torture_reset_break_info(tctx, &break_info);
+
+	qfinfo = (union smb_fileinfo) {
+		.generic.level = RAW_SFILEINFO_POSITION_INFORMATION,
+		.generic.in.file.handle = *h
+	};
+	torture_comment(tctx, "Trying getinfo\n");
+	status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_VAL(qfinfo.position_information.out.position, 0);
+
+	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_OK);
+	torture_assert_u64_not_equal_goto(tctx,
+		io.out.file.handle.data[0],
+		ref1.out.file.handle.data[0],
+		ret, done, "data 0");
+	torture_assert_u64_not_equal_goto(tctx,
+		io.out.file.handle.data[1],
+		ref1.out.file.handle.data[1],
+		ret, done, "data 1");
+	torture_wait_for_oplock_break(tctx);
+	CHECK_VAL(break_info.count, 1);
+	CHECK_VAL(break_info.level, smb2_util_oplock_level("s"));
+	torture_reset_break_info(tctx, &break_info);
+
+	/*
+	 * Resend the matching Durable V2 Create without
+	 * SMB2_FLAGS_REPLAY_OPERATION. This triggers an oplock break and still
+	 * gets NT_STATUS_DUPLICATE_OBJECTID
+	 */
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_DUPLICATE_OBJECTID);
+	torture_wait_for_oplock_break(tctx);
+	CHECK_VAL(break_info.count, 0);
+	torture_reset_break_info(tctx, &break_info);
+
+	/*
+	 * According to MS-SMB2 3.3.5.9.10 if Durable V2 Create is replayed and
+	 * FileAttributes or CreateDisposition do not match the earlier Create
+	 * request the Server fails request with
+	 * NT_STATUS_INVALID_PARAMETER. But through this test we see that server
+	 * does not really care about changed FileAttributes or
+	 * CreateDisposition.
+	 */
+	io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+	io.in.create_disposition = NTCREATEX_DISP_OPEN;
+	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_OK);
+	torture_assert_u64_not_equal_goto(tctx,
+		io.out.file.handle.data[0],
+		ref1.out.file.handle.data[0],
+		ret, done, "data 0");
+	torture_assert_u64_not_equal_goto(tctx,
+		io.out.file.handle.data[1],
+		ref1.out.file.handle.data[1],
+		ret, done, "data 1");
+	torture_wait_for_oplock_break(tctx);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+
+	smb2_util_unlink(tree, fname);
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
 struct torture_suite *torture_smb2_replay_init(void)
 {
 	struct torture_suite *suite =
@@ -2240,6 +2446,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	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);
+	torture_suite_add_1smb2_test(suite, "replay6", test_replay6);
 
 	suite->description = talloc_strdup(suite, "SMB2 REPLAY tests");
 
-- 
2.5.0

-------------- 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/20160315/f1b64c40/signature-0001.sig>


More information about the samba-technical mailing list