[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, ×up);
> + 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