s4:torture: Add Replay tests to examine server behaviour when Multiple Channels are...
authorAnubhav Rakshit <anubhav.rakshit@gmail.com>
Thu, 7 Aug 2014 07:22:50 +0000 (12:52 +0530)
committerMichael Adam <obnox@samba.org>
Fri, 19 Sep 2014 07:15:10 +0000 (09:15 +0200)
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Anubhav Rakshit <anubhav.rakshit@gmail.com>
Reviewed-by: Michael Adam <obnox@samba.org>
source4/torture/smb2/replay.c [new file with mode: 0644]
source4/torture/smb2/smb2.c
source4/torture/smb2/wscript_build

diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
new file mode 100644 (file)
index 0000000..e4f5b01
--- /dev/null
@@ -0,0 +1,824 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   test suite for SMB2 replay
+
+   Copyright (C) Anubhav Rakshit 2014
+   Copyright (C) Stefan Metzmacher 2014
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "torture/torture.h"
+#include "torture/smb2/proto.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/cmdline/popt_common.h"
+#include "auth/credentials/credentials.h"
+#include "libcli/security/security.h"
+#include "libcli/resolve/resolve.h"
+#include "lib/param/param.h"
+#include "lib/events/events.h"
+
+#define CHECK_VAL(v, correct) do { \
+       if ((v) != (correct)) { \
+               torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
+                               __location__, #v, (int)v, (int)correct); \
+               ret = false; \
+               goto done; \
+       }} while (0)
+
+#define CHECK_STATUS(status, correct) do { \
+       if (!NT_STATUS_EQUAL(status, correct)) { \
+               torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
+                      nt_errstr(status), nt_errstr(correct)); \
+               ret = false; \
+               goto done; \
+       }} while (0)
+
+#define CHECK_CREATED(__io, __created, __attribute)                    \
+       do {                                                            \
+               CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
+               CHECK_VAL((__io)->out.alloc_size, 0);                   \
+               CHECK_VAL((__io)->out.size, 0);                         \
+               CHECK_VAL((__io)->out.file_attr, (__attribute));        \
+               CHECK_VAL((__io)->out.reserved2, 0);                    \
+       } while(0)
+
+#define CHECK_HANDLE(__h1, __h2)                                       \
+       do {                                                            \
+               CHECK_VAL((__h1)->data[0], (__h2)->data[0]);            \
+               CHECK_VAL((__h1)->data[1], (__h2)->data[1]);            \
+       } while(0)
+
+#define __IO_OUT_VAL(__io1, __io2, __m) \
+       CHECK_VAL((__io1)->out.__m, (__io2)->out.__m)
+
+#define CHECK_CREATE_OUT(__io1, __io2)                         \
+       do {                                                    \
+               CHECK_HANDLE(&(__io1)->out.file.handle,         \
+                            &(__io2)->out.file.handle);        \
+               __IO_OUT_VAL(__io1, __io2, oplock_level);       \
+               __IO_OUT_VAL(__io1, __io2, create_action);      \
+               __IO_OUT_VAL(__io1, __io2, create_time);        \
+               __IO_OUT_VAL(__io1, __io2, access_time);        \
+               __IO_OUT_VAL(__io1, __io2, write_time);         \
+               __IO_OUT_VAL(__io1, __io2, change_time);        \
+               __IO_OUT_VAL(__io1, __io2, alloc_size);         \
+               __IO_OUT_VAL(__io1, __io2, size);               \
+               __IO_OUT_VAL(__io1, __io2, file_attr);          \
+               __IO_OUT_VAL(__io1, __io2, durable_open);       \
+               __IO_OUT_VAL(__io1, __io2, durable_open_v2);    \
+               __IO_OUT_VAL(__io1, __io2, persistent_open);    \
+               __IO_OUT_VAL(__io1, __io2, timeout);            \
+               __IO_OUT_VAL(__io1, __io2, blobs.num_blobs);    \
+       } while(0)
+
+#define BASEDIR "replaytestdir"
+
+static struct {
+       struct torture_context *tctx;
+       struct smb2_handle handle;
+       uint8_t level;
+       struct smb2_break br;
+       int count;
+       int failures;
+       NTSTATUS failure_status;
+} break_info;
+
+static void torture_oplock_ack_callback(struct smb2_request *req)
+{
+       NTSTATUS status;
+
+       status = smb2_break_recv(req, &break_info.br);
+       if (!NT_STATUS_IS_OK(status)) {
+               break_info.failures++;
+               break_info.failure_status = status;
+       }
+
+       return;
+}
+
+/**
+ * A general oplock break notification handler.  This should be used when a
+ * test expects to break from batch or exclusive to a lower level.
+ */
+static bool torture_oplock_ack_handler(struct smb2_transport *transport,
+                                      const struct smb2_handle *handle,
+                                      uint8_t level,
+                                      void *private_data)
+{
+       struct smb2_tree *tree = private_data;
+       const char *name;
+       struct smb2_request *req;
+
+       ZERO_STRUCT(break_info.br);
+
+       break_info.handle       = *handle;
+       break_info.level        = level;
+       break_info.count++;
+
+       switch (level) {
+       case SMB2_OPLOCK_LEVEL_II:
+               name = "level II";
+               break;
+       case SMB2_OPLOCK_LEVEL_NONE:
+               name = "none";
+               break;
+       default:
+               name = "unknown";
+               break_info.failures++;
+       }
+       torture_comment(break_info.tctx,
+                       "Acking to %s [0x%02X] in oplock handler\n",
+                       name, level);
+
+       break_info.br.in.file.handle    = *handle;
+       break_info.br.in.oplock_level   = level;
+       break_info.br.in.reserved       = 0;
+       break_info.br.in.reserved2      = 0;
+
+       req = smb2_break_send(tree, &break_info.br);
+       req->async.fn = torture_oplock_ack_callback;
+       req->async.private_data = NULL;
+       return true;
+}
+
+/**
+ * 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.
+ */
+static bool test_replay1(struct torture_context *tctx, struct smb2_tree *tree)
+{
+       bool ret = true;
+       NTSTATUS status;
+       struct smb2_handle h;
+       uint8_t buf[200];
+       struct smb2_read rd;
+       union smb_setfileinfo sfinfo;
+       union smb_fileinfo qfinfo;
+       union smb_ioctl ioctl;
+       struct smb2_lock lck;
+       struct smb2_lock_element el[2];
+       struct smb2_flush f;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       const char *fname = BASEDIR "\\replay1.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");
+       }
+
+       ZERO_STRUCT(break_info);
+       break_info.tctx = tctx;
+       tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+       tree->session->transport->oplock.private_data = tree;
+
+       status = torture_smb2_testdir(tree, BASEDIR, &h);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2_util_close(tree, h);
+
+       smb2cli_session_start_replay(tree->session->smbXcli);
+
+       torture_comment(tctx, "Try Commands with Replay Flags Enabled\n");
+
+       torture_comment(tctx, "Trying create\n");
+       status = torture_smb2_testfile(tree, fname, &h);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(break_info.count, 0);
+       /*
+        * Wireshark shows that the response has SMB2_FLAGS_REPLAY_OPERATION
+        * flags set. The server should ignore this flag.
+        */
+
+       torture_comment(tctx, "Trying write\n");
+       status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf));
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       f = (struct smb2_flush) {
+               .in.file.handle = h
+       };
+       torture_comment(tctx, "Trying flush\n");
+       status = smb2_flush(tree, &f);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       rd = (struct smb2_read) {
+               .in.file.handle = h,
+               .in.length = 10,
+               .in.offset = 0,
+               .in.min_count = 1
+       };
+       torture_comment(tctx, "Trying read\n");
+       status = smb2_read(tree, tmp_ctx, &rd);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(rd.out.data.length, 10);
+
+       sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
+       sfinfo.position_information.in.file.handle = h;
+       sfinfo.position_information.in.position = 0x1000;
+       torture_comment(tctx, "Trying setinfo\n");
+       status = smb2_setinfo_file(tree, &sfinfo);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       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, tmp_ctx, &qfinfo);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
+
+       ioctl = (union smb_ioctl) {
+               .smb2.level = RAW_IOCTL_SMB2,
+               .smb2.in.file.handle = h,
+               .smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID,
+               .smb2.in.max_response_size = 64,
+               .smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL
+       };
+       torture_comment(tctx, "Trying ioctl\n");
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       lck = (struct smb2_lock) {
+               .in.locks = el,
+               .in.lock_count = 0x0001,
+               .in.lock_sequence = 0x00000000,
+               .in.file.handle = h
+       };
+       el[0].reserved          = 0x00000000;
+       el[0].flags             = SMB2_LOCK_FLAG_EXCLUSIVE |
+               SMB2_LOCK_FLAG_FAIL_IMMEDIATELY;
+
+       torture_comment(tctx, "Trying lock\n");
+       el[0].offset            = 0x0000000000000000;
+       el[0].length            = 0x0000000000000100;
+       status = smb2_lock(tree, &lck);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       lck.in.file.handle      = h;
+       el[0].flags             = SMB2_LOCK_FLAG_UNLOCK;
+       status = smb2_lock(tree, &lck);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       CHECK_VAL(break_info.count, 0);
+done:
+       smb2cli_session_stop_replay(tree->session->smbXcli);
+       smb2_util_close(tree, h);
+       smb2_deltree(tree, BASEDIR);
+
+       talloc_free(tmp_ctx);
+
+       return ret;
+}
+
+/**
+ * Test Durablity V2 Create Replay Detection on Single Channel. Also verify that
+ * regular creates can not be replayed.
+ */
+static bool test_replay2(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, ref2;
+       struct GUID create_guid = GUID_random();
+       uint32_t perms = 0;
+       bool ret = true;
+       const char *fname = BASEDIR "\\replay2.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");
+       }
+
+       ZERO_STRUCT(break_info);
+       break_info.tctx = tctx;
+       tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+       tree->session->transport->oplock.private_data = tree;
+
+       torture_comment(tctx, "Replay of DurableHandleReqV2 on Single "
+                             "Channel\n");
+       smb2_util_unlink(tree, fname);
+       status = torture_smb2_testdir(tree, BASEDIR, &_h);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2_util_close(tree, _h);
+       CHECK_VAL(break_info.count, 0);
+
+       smb2_oplock_create_share(&io, fname,
+                       smb2_util_share_access(""),
+                       smb2_util_oplock_level("b"));
+       io.in.durable_open = false;
+       io.in.durable_open_v2 = true;
+       io.in.persistent_open = false;
+       io.in.create_guid = create_guid;
+       io.in.timeout = UINT32_MAX;
+
+       status = smb2_create(tree, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       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);
+       CHECK_VAL(io.out.timeout, io.in.timeout);
+
+       /*
+        * Replay Durable V2 Create on single channel
+        */
+       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);
+       CHECK_VAL(break_info.count, 0);
+
+       /*
+        * See how server behaves if we change some of the Create params while
+        * Replaying. Change Share Access and Oplock Level. It seems the server
+        * does not care for change in these parameters. The server seems to
+        * only care for the File Name and GUID
+        */
+       smb2_oplock_create_share(&io, fname,
+                       smb2_util_share_access("RWD"),
+                       smb2_util_oplock_level(""));
+       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;
+
+       /*
+        * The output will just react on the
+        * input, but it doesn't change the oplock
+        * or share access values on the existing open
+        */
+       ref2 = ref1;
+       ref2.out.oplock_level = smb2_util_oplock_level("");
+       ref2.out.durable_open_v2 = false;
+       ref2.out.timeout = 0;
+       ref2.out.blobs.num_blobs = 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);
+       CHECK_CREATE_OUT(&io, &ref2);
+       CHECK_VAL(break_info.count, 0);
+
+       /*
+        * This is a normal open, which triggers an oplock
+        * break and still gets NT_STATUS_SHARING_VIOLATION
+        */
+       io = ref1;
+       io.in.durable_open_v2 = false;
+       status = smb2_create(tree, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
+       CHECK_VAL(break_info.count, 1);
+       CHECK_HANDLE(&break_info.handle, &ref1.out.file.handle);
+       CHECK_VAL(break_info.level, smb2_util_oplock_level("s"));
+       ZERO_STRUCT(break_info);
+
+       smb2_util_close(tree, *h);
+       h = NULL;
+       status = smb2_util_unlink(tree, fname);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(break_info.count, 0);
+
+       /*
+        * No Replay detection for regular Creates
+        */
+       perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE |
+               SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE |
+               SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA |
+               SEC_FILE_WRITE_DATA;
+
+       io = (struct smb2_create) {
+               .in.desired_access  = perms,
+               .in.file_attributes = 0,
+               .in.create_disposition = NTCREATEX_DISP_CREATE,
+               .in.share_access    = NTCREATEX_SHARE_ACCESS_DELETE,
+               .in.create_options  = 0x0,
+               .in.fname   = fname
+       };
+
+       status = smb2_create(tree, tctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(break_info.count, 0);
+       _h = io.out.file.handle;
+       h = &_h;
+       CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+
+       torture_comment(tctx, "No Replay Detection for regular Create\n");
+       /*
+        * Now replay the same create
+        */
+       smb2cli_session_start_replay(tree->session->smbXcli);
+       status = smb2_create(tree, tctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION);
+       CHECK_VAL(break_info.count, 0);
+
+done:
+       smb2cli_session_stop_replay(tree->session->smbXcli);
+
+       if (h != NULL) {
+               smb2_util_close(tree, *h);
+       }
+       smb2_deltree(tree, BASEDIR);
+
+       talloc_free(tree);
+       talloc_free(mem_ctx);
+
+       return ret;
+}
+
+/**
+ * Test Durablity V2 Create Replay Detection on Multi Channel
+ */
+static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1)
+{
+       const char *host = torture_setting_string(tctx, "host", NULL);
+       const char *share = torture_setting_string(tctx, "share", NULL);
+       struct cli_credentials *credentials = cmdline_credentials;
+       NTSTATUS status;
+       TALLOC_CTX *mem_ctx = talloc_new(tctx);
+       struct smb2_handle _h;
+       struct smb2_handle *h = NULL;
+       struct smb2_create io;
+       struct GUID create_guid = GUID_random();
+       bool ret = true;
+       const char *fname = BASEDIR "\\replay3.dat";
+       struct smb2_tree *tree2 = NULL;
+       struct smb2_transport *transport1 = tree1->session->transport;
+       struct smb2_transport *transport2 = NULL;
+       struct smb2_session *session1_1 = tree1->session;
+       struct smb2_session *session1_2 = NULL;
+
+       if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
+               torture_skip(tctx, "SMB 3.X Dialect family required for "
+                                  "Replay tests\n");
+       }
+
+       ZERO_STRUCT(break_info);
+       break_info.tctx = tctx;
+       transport1->oplock.handler = torture_oplock_ack_handler;
+       transport1->oplock.private_data = tree1;
+
+       torture_comment(tctx, "Replay of DurableHandleReqV2 on Multi "
+                             "Channel\n");
+       status = torture_smb2_testdir(tree1, BASEDIR, &_h);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2_util_close(tree1, _h);
+       smb2_util_unlink(tree1, fname);
+       CHECK_VAL(break_info.count, 0);
+
+       /*
+        * use the 1st channel, 1st session
+        */
+       smb2_oplock_create_share(&io, fname,
+                       smb2_util_share_access(""),
+                       smb2_util_oplock_level("b"));
+       io.in.durable_open = false;
+       io.in.durable_open_v2 = true;
+       io.in.persistent_open = false;
+       io.in.create_guid = create_guid;
+       io.in.timeout = UINT32_MAX;
+
+       tree1->session = session1_1;
+       status = smb2_create(tree1, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       _h = io.out.file.handle;
+       h = &_h;
+       CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+       CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+       CHECK_VAL(io.out.durable_open, false);
+       CHECK_VAL(io.out.durable_open_v2, true);
+       CHECK_VAL(io.out.timeout, io.in.timeout);
+       CHECK_VAL(break_info.count, 0);
+
+       status = smb2_connect(tctx,
+                       host,
+                       lpcfg_smb_ports(tctx->lp_ctx),
+                       share,
+                       lpcfg_resolve_context(tctx->lp_ctx),
+                       credentials,
+                       &tree2,
+                       tctx->ev,
+                       &transport1->options,
+                       lpcfg_socket_options(tctx->lp_ctx),
+                       lpcfg_gensec_settings(tctx, tctx->lp_ctx)
+                       );
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                       "smb2_connect failed");
+       transport2 = tree2->session->transport;
+
+       transport2->oplock.handler = torture_oplock_ack_handler;
+       transport2->oplock.private_data = tree2;
+
+       /*
+        * Now bind the 1st session to 2nd transport channel
+        */
+       session1_2 = smb2_session_channel(transport2,
+                       lpcfg_gensec_settings(tctx, tctx->lp_ctx),
+                       tree2, session1_1);
+       torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed");
+
+       status = smb2_session_setup_spnego(session1_2,
+                       cmdline_credentials,
+                       0 /* previous_session_id */);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       /*
+        * use the 2nd channel, 1st session
+        */
+       tree1->session = session1_2;
+       smb2cli_session_start_replay(tree1->session->smbXcli);
+       status = smb2_create(tree1, mem_ctx, &io);
+       smb2cli_session_stop_replay(tree1->session->smbXcli);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       _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);
+       CHECK_VAL(io.out.timeout, io.in.timeout);
+       CHECK_VAL(break_info.count, 0);
+
+       tree1->session = session1_1;
+       smb2_util_close(tree1, *h);
+       h = NULL;
+
+done:
+       talloc_free(tree2);
+       tree1->session = session1_1;
+
+       if (h != NULL) {
+               smb2_util_close(tree1, *h);
+       }
+
+       smb2_util_unlink(tree1, fname);
+       smb2_deltree(tree1, BASEDIR);
+
+       talloc_free(tree1);
+       talloc_free(mem_ctx);
+
+       return ret;
+}
+
+/**
+ * Test Multichannel IO Ordering using ChannelSequence/Channel Epoch number
+ */
+static bool test_replay4(struct torture_context *tctx, struct smb2_tree *tree1)
+{
+       const char *host = torture_setting_string(tctx, "host", NULL);
+       const char *share = torture_setting_string(tctx, "share", NULL);
+       struct cli_credentials *credentials = cmdline_credentials;
+       NTSTATUS status;
+       TALLOC_CTX *mem_ctx = talloc_new(tctx);
+       struct smb2_handle _h1;
+       struct smb2_handle *h1 = NULL;
+       struct smb2_create io;
+       struct GUID create_guid = GUID_random();
+       uint8_t buf[64];
+       struct smb2_read rd;
+       union smb_setfileinfo sfinfo;
+       bool ret = true;
+       const char *fname = BASEDIR "\\replay4.dat";
+       struct smb2_tree *tree2 = NULL;
+       struct smb2_transport *transport1 = tree1->session->transport;
+       struct smb2_transport *transport2 = NULL;
+       struct smb2_session *session1_1 = tree1->session;
+       struct smb2_session *session1_2 = NULL;
+       uint16_t curr_cs;
+
+       if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
+               torture_skip(tctx, "SMB 3.X Dialect family required for "
+                                  "Replay tests\n");
+       }
+
+       ZERO_STRUCT(break_info);
+       break_info.tctx = tctx;
+       transport1->oplock.handler = torture_oplock_ack_handler;
+       transport1->oplock.private_data = tree1;
+
+       torture_comment(tctx, "IO Ordering for Multi Channel\n");
+       status = torture_smb2_testdir(tree1, BASEDIR, &_h1);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2_util_close(tree1, _h1);
+       smb2_util_unlink(tree1, fname);
+       CHECK_VAL(break_info.count, 0);
+
+       /*
+        * use the 1st channel, 1st session
+        */
+
+       smb2_oplock_create_share(&io, fname,
+                       smb2_util_share_access(""),
+                       smb2_util_oplock_level("b"));
+       io.in.durable_open = false;
+       io.in.durable_open_v2 = true;
+       io.in.persistent_open = false;
+       io.in.create_guid = create_guid;
+       io.in.timeout = UINT32_MAX;
+
+       tree1->session = session1_1;
+       status = smb2_create(tree1, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       _h1 = io.out.file.handle;
+       h1 = &_h1;
+       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);
+       CHECK_VAL(io.out.timeout, io.in.timeout);
+       CHECK_VAL(break_info.count, 0);
+
+       status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf));
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       /*
+        * Increment ChannelSequence so that server thinks that there's a
+        * Channel Failure
+        */
+       smb2cli_session_increment_channel_sequence(tree1->session->smbXcli);
+
+       /*
+        * Perform a Read with incremented ChannelSequence
+        */
+       rd = (struct smb2_read) {
+               .in.file.handle = *h1,
+               .in.length = sizeof(buf),
+               .in.offset = 0
+       };
+       status = smb2_read(tree1, tree1, &rd);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       /*
+        * Performing a Write with Stale ChannelSequence is not allowed by
+        * server
+        */
+       curr_cs = smb2cli_session_reset_channel_sequence(
+                                               tree1->session->smbXcli, 0);
+       status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf));
+       CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE);
+
+       /*
+        * Performing a Write Replay with Stale ChannelSequence is not allowed
+        * by server
+        */
+       smb2cli_session_start_replay(tree1->session->smbXcli);
+       smb2cli_session_reset_channel_sequence(tree1->session->smbXcli, 0);
+       status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf));
+       smb2cli_session_stop_replay(tree1->session->smbXcli);
+       CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE);
+
+       /*
+        * Performing a SetInfo with stale ChannelSequence is not allowed by
+        * server
+        */
+       ZERO_STRUCT(sfinfo);
+       sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
+       sfinfo.generic.in.file.handle = *h1;
+       sfinfo.position_information.in.position = 0x1000;
+       status = smb2_setinfo_file(tree1, &sfinfo);
+       CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE);
+
+       /*
+        * Performing a Read with stale ChannelSequence is allowed
+        */
+       rd = (struct smb2_read) {
+               .in.file.handle = *h1,
+               .in.length = ARRAY_SIZE(buf),
+               .in.offset = 0
+       };
+       status = smb2_read(tree1, tree1, &rd);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       status = smb2_connect(tctx,
+                       host,
+                       lpcfg_smb_ports(tctx->lp_ctx),
+                       share,
+                       lpcfg_resolve_context(tctx->lp_ctx),
+                       credentials,
+                       &tree2,
+                       tctx->ev,
+                       &transport1->options,
+                       lpcfg_socket_options(tctx->lp_ctx),
+                       lpcfg_gensec_settings(tctx, tctx->lp_ctx)
+                       );
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                       "smb2_connect failed");
+       transport2 = tree2->session->transport;
+
+       transport2->oplock.handler = torture_oplock_ack_handler;
+       transport2->oplock.private_data = tree2;
+
+       /*
+        * Now bind the 1st session to 2nd transport channel
+        */
+       session1_2 = smb2_session_channel(transport2,
+                       lpcfg_gensec_settings(tctx, tctx->lp_ctx),
+                       tree2, session1_1);
+       torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed");
+
+       status = smb2_session_setup_spnego(session1_2,
+                       cmdline_credentials,
+                       0 /* previous_session_id */);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       /*
+        * use the 2nd channel, 1st session
+        */
+       tree1->session = session1_2;
+
+       /*
+        * Write Replay with Correct ChannelSequence is allowed by the server
+        */
+       smb2cli_session_start_replay(tree1->session->smbXcli);
+       smb2cli_session_reset_channel_sequence(tree1->session->smbXcli,
+                                              curr_cs);
+       status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf));
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2cli_session_stop_replay(tree1->session->smbXcli);
+
+       /*
+        * See what happens if we change the Buffer and perform a Write Replay.
+        * This is to show that Write Replay does not really care about the data
+        */
+       memset(buf, 'r', ARRAY_SIZE(buf));
+       smb2cli_session_start_replay(tree1->session->smbXcli);
+       status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf));
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2cli_session_stop_replay(tree1->session->smbXcli);
+
+       /*
+        * Read back from File to verify what was written
+        */
+       rd = (struct smb2_read) {
+               .in.file.handle = *h1,
+               .in.length = ARRAY_SIZE(buf),
+               .in.offset = 0
+       };
+       status = smb2_read(tree1, tree1, &rd);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       if ((rd.out.data.length != ARRAY_SIZE(buf)) ||
+                       memcmp(rd.out.data.data, buf, ARRAY_SIZE(buf))) {
+               torture_comment(tctx, "Write Replay Data Mismatch\n");
+       }
+
+       tree1->session = session1_1;
+       smb2_util_close(tree1, *h1);
+       h1 = NULL;
+
+       CHECK_VAL(break_info.count, 0);
+done:
+       talloc_free(tree2);
+       tree1->session = session1_1;
+
+       if (h1 != NULL) {
+               smb2_util_close(tree1, *h1);
+       }
+
+       smb2_util_unlink(tree1, fname);
+       smb2_deltree(tree1, BASEDIR);
+
+       talloc_free(tree1);
+       talloc_free(mem_ctx);
+
+       return ret;
+}
+
+struct torture_suite *torture_smb2_replay_init(void)
+{
+       struct torture_suite *suite =
+               torture_suite_create(talloc_autofree_context(), "replay");
+
+       torture_suite_add_1smb2_test(suite, "replay1", test_replay1);
+       torture_suite_add_1smb2_test(suite, "replay2", test_replay2);
+       torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
+       torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
+
+       suite->description = talloc_strdup(suite, "SMB2 REPLAY tests");
+
+       return suite;
+}
index 19d7e4ae92a70c4bb566007c7965032ec872495a..853c4f62802d771a4df70ad1fbc89a936411e6b1 100644 (file)
@@ -1,18 +1,18 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
    SMB torture tester
    Copyright (C) Jelmer Vernooij 2006
-   
+
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
@@ -61,9 +61,9 @@ struct torture_test *torture_suite_add_1smb2_test(struct torture_suite *suite,
                                                  bool (*run)(struct torture_context *,
                                                              struct smb2_tree *))
 {
-       struct torture_test *test; 
+       struct torture_test *test;
        struct torture_tcase *tcase;
-       
+
        tcase = torture_suite_add_tcase(suite, name);
 
        test = talloc(tcase, struct torture_test);
@@ -168,6 +168,7 @@ NTSTATUS torture_smb2_init(void)
        torture_suite_add_1smb2_test(suite, "bench-oplock", test_smb2_bench_oplock);
        torture_suite_add_1smb2_test(suite, "hold-oplock", test_smb2_hold_oplock);
        torture_suite_add_suite(suite, torture_smb2_session_init());
+       torture_suite_add_suite(suite, torture_smb2_replay_init());
 
        torture_suite_add_suite(suite, torture_smb2_doc_init());
 
index 432e872d65af0d2f49761be76a43f4078df495d0..9ccf85b950b4ba0f73448243c9a49c96063fd04e 100644 (file)
@@ -4,7 +4,7 @@ bld.SAMBA_MODULE('TORTURE_SMB2',
        source='''connect.c scan.c util.c getinfo.c setinfo.c lock.c notify.c
        smb2.c durable_open.c durable_v2_open.c oplock.c dir.c lease.c create.c
        acls.c read.c compound.c streams.c ioctl.c rename.c
-       session.c delete-on-close.c''',
+       session.c delete-on-close.c replay.c''',
        allow_warnings=True,
        subsystem='smbtorture',
        deps='LIBCLI_SMB2 POPT_CREDENTIALS torture NDR_IOCTL',