diff options
-rw-r--r-- | source4/torture/smb2/replay.c | 824 | ||||
-rw-r--r-- | source4/torture/smb2/smb2.c | 13 | ||||
-rw-r--r-- | source4/torture/smb2/wscript_build | 2 |
3 files changed, 832 insertions, 7 deletions
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c new file mode 100644 index 0000000000..e4f5b01b3b --- /dev/null +++ b/source4/torture/smb2/replay.c @@ -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; +} diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c index 19d7e4ae92..853c4f6280 100644 --- a/source4/torture/smb2/smb2.c +++ b/source4/torture/smb2/smb2.c @@ -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()); diff --git a/source4/torture/smb2/wscript_build b/source4/torture/smb2/wscript_build index 432e872d65..9ccf85b950 100644 --- a/source4/torture/smb2/wscript_build +++ b/source4/torture/smb2/wscript_build @@ -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', |