[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, ×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
-------------- 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