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

Jeremy Allison jra at samba.org
Mon Mar 21 22:01:03 UTC 2016


On Tue, Mar 15, 2016 at 12:52:25PM +0100, Michael Adam wrote:
> 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!

Finally got the time to go through this carefully with a copy
of the latest SMB2 spec to hand.

Great work !!

Pushed.

Jeremy.

> 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
> 






More information about the samba-technical mailing list