From 31f33a3f3996a5fff9833540c8227600f4aa2a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Wed, 27 Jan 2016 16:18:25 +0100 Subject: [PATCH 01/20] s3:smbXsrv.idl: add 8 byte channel_sequence number and request counters to IDL. Guenther Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison --- 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.5 From 088468195b7f7f04eab0ce6fb928bda1c703e2fa Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Wed, 24 Feb 2016 15:51:14 +0100 Subject: [PATCH 02/20] 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 Signed-off-by: Michael Adam Signed-off-by: Stefan Metzmacher Reviewed-by: Jeremy Allison --- 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.5 From ae6967ea3e39a1a5401be4a4c969b467dd22dce4 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 12:36:59 +0100 Subject: [PATCH 03/20] 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 Reviewed-by: Jeremy Allison --- 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.5 From 71d2b190646bdf5fce65a776dfe6873da8d82479 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Wed, 24 Feb 2016 15:54:41 +0100 Subject: [PATCH 04/20] smbd:smb2: implement channel sequence checks and request counters in dispatch Pair-Programmed-With: Stefan Metzmacher Pair-Programmed-With: Guenther Deschner Signed-off-by: Michael Adam Signed-off-by: Stefan Metzmacher Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison --- 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.5 From 7dbb1707d96e39bed8898db08339d3b2d768c87c Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 23 Feb 2016 20:54:34 +0100 Subject: [PATCH 05/20] 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 Signed-off-by: Michael Adam Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison --- 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.5 From f81f3a2d78832258b09bcc63d5cce2b4594cbbc8 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Sat, 27 Feb 2016 14:02:02 +0100 Subject: [PATCH 06/20] smbd:smb2: add some asserts before decrementing the counters Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- 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.5 From ccda60ed9b33bb22ec2e162401a949aeaa631c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Tue, 1 Mar 2016 15:15:10 +0100 Subject: [PATCH 07/20] libcli:smb:smbXcli_base: add smb2cli_session_current_channel_sequence() call. Guenther Signed-off-by: Guenther Deschner Reviewed-by: Michael Adam Reviewed-by: Jeremy Allison --- 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.5 From 2b799880b91f2ee44531644c62916f9a50531d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Thu, 25 Feb 2016 11:15:06 +0100 Subject: [PATCH 08/20] torture:smb2: add test for checking sequence number wrap around. Guenther Signed-off-by: Guenther Deschner Reviewed-by: Michael Adam Reviewed-by: Jeremy Allison --- 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 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 Date: Wed, 24 Feb 2016 19:23:21 +0100 Subject: [PATCH 09/20] 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 Reviewed-by: Jeremy Allison --- 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.5 From e095a61c4b36e71b03d8afc724da09c91603a29b Mon Sep 17 00:00:00 2001 From: Anubhav Rakshit Date: Thu, 30 Oct 2014 13:20:57 +0530 Subject: [PATCH 10/20] torture:smb2: Add test replay6 to verify Error Codes for DurableHandleReqV2 replay Pair-Programmed-With: Stefan Metzmacher Pair-Programmed-With: Guenther Deschner Pair-Programmed-With: Michael Adam Signed-off-by: Anubhav Rakshit Signed-off-by: Stefan Metzmacher Signed-off-by: Guenther Deschner Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- 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.5 From b7186a00bb985b5b878ccf2c47a1ac4e0c2a7c79 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 09:35:03 +0100 Subject: [PATCH 11/20] torture:smb2: use assert, not warning in error case in durable-open.reopen1a Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_open.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index fe0a326..11388ef 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -464,13 +464,9 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); - if (!torture_smb2_connection_ext(tctx, previous_session_id, - &options, &tree2)) - { - torture_warning(tctx, "couldn't reconnect, bailing\n"); - ret = false; - goto done; - } + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "could not reconnect"); /* * check that this has deleted the old session -- 2.5.5 From 37c05f4ade3a2d0e5076941095e7d5c09631ac1b Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Fri, 4 Mar 2016 22:55:40 +0100 Subject: [PATCH 12/20] torture:smb2: fix crashes in smb2.durable-open.reopen1a test If the test failed too early, we dereferenced tree2 which was still NULL. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_open.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index 11388ef..e8a82c0 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -479,6 +479,8 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, status = smb2_create(tree, mem_ctx, &io2); CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + /* * but a durable reconnect on the new session succeeds: */ @@ -495,15 +497,19 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, h = &_h; done: - if (h != NULL) { - smb2_util_close(tree2, *h); + if (tree == NULL) { + tree = tree2; } - smb2_util_unlink(tree2, fname); - - talloc_free(tree2); + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + h = NULL; + } + smb2_util_unlink(tree, fname); - talloc_free(tree); + talloc_free(tree); + } talloc_free(mem_ctx); -- 2.5.5 From 505546be57043c81e38ddcfe1b8bcdf0ad5963b0 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Thu, 17 Mar 2016 02:45:16 +0100 Subject: [PATCH 13/20] torture:smb2: durable-open.reopen1a only needs one io struct Using two is confusing. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_open.c | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index e8a82c0..584f046 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -431,7 +431,7 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, char fname[256]; struct smb2_handle _h; struct smb2_handle *h = NULL; - struct smb2_create io1, io2; + struct smb2_create io; bool ret = true; struct smb2_tree *tree2 = NULL; uint64_t previous_session_id; @@ -445,18 +445,18 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, smb2_util_unlink(tree, fname); - smb2_oplock_create_share(&io1, fname, + smb2_oplock_create_share(&io, fname, smb2_util_share_access(""), smb2_util_oplock_level("b")); - io1.in.durable_open = true; + io.in.durable_open = true; - status = smb2_create(tree, mem_ctx, &io1); + status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - _h = io1.out.file.handle; + _h = io.out.file.handle; h = &_h; - CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io1.out.durable_open, true); - CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); /* * a session reconnect on a second tcp connection @@ -472,11 +472,11 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, * check that this has deleted the old session */ - ZERO_STRUCT(io2); - io2.in.fname = fname; - io2.in.durable_handle = h; + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; - status = smb2_create(tree, mem_ctx, &io2); + status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); TALLOC_FREE(tree); @@ -485,15 +485,15 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, * but a durable reconnect on the new session succeeds: */ - ZERO_STRUCT(io2); - io2.in.fname = fname; - io2.in.durable_handle = h; + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; - status = smb2_create(tree2, mem_ctx, &io2); + status = smb2_create(tree2, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); - _h = io2.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; h = &_h; done: -- 2.5.5 From 7b557617e48e17c2f02801fcd089719f7994488b Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 08:59:53 +0100 Subject: [PATCH 14/20] torture:smb2: for oplocks, durable reconnect works with different client guid in durabble-open.reopen1a test Try both original and a different client guid. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_open.c | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index 584f046..b74b5c5 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -434,10 +434,13 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, struct smb2_create io; bool ret = true; struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; uint64_t previous_session_id; struct smbcli_options options; + struct GUID orig_client_guid; options = tree->session->transport->options; + orig_client_guid = options.client_guid; /* Choose a random name in case the state is left a little funky. */ snprintf(fname, 256, "durable_open_reopen1a_%s.dat", @@ -464,6 +467,9 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + /* for oplocks, the client guid can be different: */ + options.client_guid = GUID_random(); + ret = torture_smb2_connection_ext(tctx, previous_session_id, &options, &tree2); torture_assert_goto(tctx, ret, ret, done, "could not reconnect"); @@ -496,11 +502,56 @@ static bool test_durable_open_reopen1a(struct torture_context *tctx, _h = io.out.file.handle; h = &_h; + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree2->session->smbXcli); + + /* the original client_guid works just the same */ + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "could not reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + done: if (tree == NULL) { tree = tree2; } + if (tree == NULL) { + tree = tree3; + } + if (tree != NULL) { if (h != NULL) { smb2_util_close(tree, *h); -- 2.5.5 From 3e90abe6704a1af62ce22a6e62539a28a8abfe22 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 10:02:14 +0100 Subject: [PATCH 15/20] torture:smb2: add durable-open.reopen1a-lease Lease variant of the reopen1a test which tests the relevance of the client guid. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- selftest/knownfail | 1 + source4/torture/smb2/durable_open.c | 170 ++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/selftest/knownfail b/selftest/knownfail index 0950ef0..d223871 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -193,6 +193,7 @@ ^samba3.smb2.notify.rec ^samba3.smb2.durable-open.delete_on_close2 ^samba3.smb2.durable-v2-open.app-instance +^samba3.smb2.durable-open.reopen1a-lease\(ad_dc\)$ ^samba4.smb2.ioctl.req_resume_key\(ad_dc_ntvfs\) # not supported by s4 ntvfs server ^samba4.smb2.ioctl.copy_chunk_\w*\(ad_dc_ntvfs\) # not supported by s4 ntvfs server ^samba3.smb2.dir.one diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index b74b5c5..7b59566 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -568,6 +568,175 @@ done: } /** + * lease variant of reopen1a + * + * Basic test for doing a durable open and doing a session + * reconnect while the first session is still active and the + * handle is still open in the client. + * This closes the original session and a durable reconnect on + * the new session succeeds depending on the client guid: + * + * Durable reconnect on a session with a different client guid fails. + * Durable reconnect on a session with the original client guid succeeds. + */ +bool test_durable_open_reopen1a_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1a_lease_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RWH")); + io.in.durable_open = true; + + status = smb2_create(tree, 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.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* + * a session reconnect on a second tcp connection + * with a different client_guid does not allow + * the durable reconnect. + */ + + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + + + /* + * but a durable reconnect on the new session with the wrong + * client guid fails + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * now a session reconnect on a second tcp connection + * with original client_guid allows the durable reconnect. + */ + + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + * In this case, a durable reconnect attempt with the + * correct client_guid yields a different error code. + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); /* no dh response context... */ + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + + +/** * basic test for doing a durable open * tcp disconnect, reconnect, do a durable reopen (succeeds) */ @@ -2589,6 +2758,7 @@ struct torture_suite *torture_smb2_durable_open_init(void) torture_suite_add_1smb2_test(suite, "open-lease", test_durable_open_open_lease); torture_suite_add_1smb2_test(suite, "reopen1", test_durable_open_reopen1); torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_open_reopen1a); + torture_suite_add_1smb2_test(suite, "reopen1a-lease", test_durable_open_reopen1a_lease); torture_suite_add_1smb2_test(suite, "reopen2", test_durable_open_reopen2); torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_open_reopen2_lease); torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_open_reopen2_lease_v2); -- 2.5.5 From 186cd708291641c507cda0c89cc9c24900634ed2 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 09:39:43 +0100 Subject: [PATCH 16/20] torture:smb2: use assert, not warning in error case in durable-v2-open.reopen1a Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_v2_open.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index f3ec344..a80506a 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -629,13 +629,9 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); - if (!torture_smb2_connection_ext(tctx, previous_session_id, - &options, &tree2)) - { - torture_warning(tctx, "couldn't reconnect, bailing\n"); - ret = false; - goto done; - } + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); /* * check that this has deleted the old session -- 2.5.5 From b0f592d05f66114427b26cd4aeea918e41a2e952 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 09:44:06 +0100 Subject: [PATCH 17/20] torture:smb2: fix crashes in smb2.durable-v2-open.reopen1a test If the test failed too early, we dereferenced tree2 which was still NULL. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_v2_open.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index a80506a..de24c8f 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -644,6 +644,8 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + /* * but a durable reconnect on the new session succeeds: */ @@ -664,13 +666,19 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, h = &_h; done: - if (h != NULL) { - smb2_util_close(tree, *h); + if (tree == NULL) { + tree = tree2; } - smb2_util_unlink(tree, fname); + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } - talloc_free(tree); + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } talloc_free(mem_ctx); -- 2.5.5 From cd2298087583de8d6b352dbbbecb00b1dbffe25c Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Thu, 17 Mar 2016 02:35:35 +0100 Subject: [PATCH 18/20] torture:smb2: get rid of supefluous io2 var in durable-v2-open.reopen1a Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_v2_open.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index de24c8f..2528187 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -588,7 +588,7 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, char fname[256]; struct smb2_handle _h; struct smb2_handle *h = NULL; - struct smb2_create io, io2; + struct smb2_create io; struct GUID create_guid = GUID_random(); bool ret = true; struct smb2_tree *tree2 = NULL; @@ -650,19 +650,19 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, * but a durable reconnect on the new session succeeds: */ - ZERO_STRUCT(io2); - io2.in.fname = ""; - io2.in.durable_handle_v2 = h; - io2.in.create_guid = create_guid; - status = smb2_create(tree2, mem_ctx, &io2); + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree2, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); - CHECK_VAL(io2.out.durable_open, false); - CHECK_VAL(io2.out.durable_open_v2, false); /* no dh2q response blob */ - CHECK_VAL(io2.out.persistent_open, false); - CHECK_VAL(io2.out.timeout, io.in.timeout); - _h = io2.out.file.handle; + CHECK_CREATED(&io, EXISTED, 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, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, io.in.timeout); + _h = io.out.file.handle; h = &_h; done: -- 2.5.5 From b853040d5f27f0faf8d9a6071e6e6e6b2f840360 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 09:02:28 +0100 Subject: [PATCH 19/20] torture:smb2: for oplocks, durable reconnect works with different client-guid for durable-v2-open.reopen1a Try both different and original client guid. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source4/torture/smb2/durable_v2_open.c | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index 2528187..e48fb9b 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -592,10 +592,13 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, struct GUID create_guid = GUID_random(); bool ret = true; struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; uint64_t previous_session_id; struct smbcli_options options; + struct GUID orig_client_guid; options = tree->session->transport->options; + orig_client_guid = options.client_guid; /* Choose a random name in case the state is left a little funky. */ snprintf(fname, 256, "durable_v2_open_reopen1a_%s.dat", @@ -629,6 +632,9 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + /* for oplocks, the client guid can be different: */ + options.client_guid = GUID_random(); + ret = torture_smb2_connection_ext(tctx, previous_session_id, &options, &tree2); torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); @@ -665,11 +671,59 @@ bool test_durable_v2_open_reopen1a(struct torture_context *tctx, _h = io.out.file.handle; h = &_h; + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree2->session->smbXcli); + + /* it works the same with the original guid */ + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, 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, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, io.in.timeout); + _h = io.out.file.handle; + h = &_h; + done: if (tree == NULL) { tree = tree2; } + if (tree == NULL) { + tree = tree3; + } + if (tree != NULL) { if (h != NULL) { smb2_util_close(tree, *h); -- 2.5.5 From e9586a653c62b996f1a183c897308ea0794c1cb7 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 09:06:56 +0100 Subject: [PATCH 20/20] torture:smb2: add durable-v2-open.reopen1a-lease Lease variant of the reopen1a test which tests the relevance of the client guid. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Tue Mar 22 03:47:02 CET 2016 on sn-devel-144 --- selftest/knownfail | 1 + source4/torture/smb2/durable_v2_open.c | 183 +++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/selftest/knownfail b/selftest/knownfail index d223871..83cf2d6 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -194,6 +194,7 @@ ^samba3.smb2.durable-open.delete_on_close2 ^samba3.smb2.durable-v2-open.app-instance ^samba3.smb2.durable-open.reopen1a-lease\(ad_dc\)$ +^samba3.smb2.durable-v2-open.reopen1a-lease\(ad_dc\)$ ^samba4.smb2.ioctl.req_resume_key\(ad_dc_ntvfs\) # not supported by s4 ntvfs server ^samba4.smb2.ioctl.copy_chunk_\w*\(ad_dc_ntvfs\) # not supported by s4 ntvfs server ^samba3.smb2.dir.one diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index e48fb9b..b7d8920 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -740,6 +740,188 @@ done: } /** + * lease variant of reopen1a + * + * Basic test for doing a durable open and doing a session + * reconnect while the first session is still active and the + * handle is still open in the client. + * This closes the original session and a durable reconnect on + * the new session succeeds depending on the client guid: + * + * Durable reconnect on a session with a different client guid fails. + * Durable reconnect on a session with the original client guid succeeds. + */ +bool test_durable_v2_open_reopen1a_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1a_lease_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RWH")); + 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); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, io.in.timeout); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* + * a session reconnect on a second tcp connection + * with a different client_guid does not allow + * the durable reconnect. + */ + + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + + /* + * but a durable reconnect on the new session with the wrong + * client guid fails + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + + /* + * now a session reconnect on a second tcp connection + * with original client_guid allows the durable reconnect. + */ + + options.client_guid = orig_client_guid; + //options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, io.in.timeout); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** * basic test for doing a durable open * tcp disconnect, reconnect, do a durable reopen (succeeds) */ @@ -1832,6 +2014,7 @@ struct torture_suite *torture_smb2_durable_v2_open_init(void) torture_suite_add_1smb2_test(suite, "open-lease", test_durable_v2_open_lease); torture_suite_add_1smb2_test(suite, "reopen1", test_durable_v2_open_reopen1); torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_v2_open_reopen1a); + torture_suite_add_1smb2_test(suite, "reopen1a-lease", test_durable_v2_open_reopen1a_lease); torture_suite_add_1smb2_test(suite, "reopen2", test_durable_v2_open_reopen2); torture_suite_add_1smb2_test(suite, "reopen2b", test_durable_v2_open_reopen2b); torture_suite_add_1smb2_test(suite, "reopen2c", test_durable_v2_open_reopen2c); -- 2.5.5 From fb381d885b0367c0b6f163ce1661b4cc4ae5f5ed Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 22 Mar 2016 16:03:58 +0100 Subject: [PATCH] torture: Fix the O3 developer build Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Tue Mar 22 22:48:52 CET 2016 on sn-devel-144 --- source4/torture/smb2/replay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c index 91bb568..26b5583 100644 --- a/source4/torture/smb2/replay.c +++ b/source4/torture/smb2/replay.c @@ -1497,7 +1497,7 @@ static bool test_channel_sequence_table(struct torture_context *tctx, bool do_replay, uint16_t opcode) { - NTSTATUS status; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; TALLOC_CTX *mem_ctx = talloc_new(tctx); struct smb2_handle handle; struct smb2_handle *phandle = NULL; -- 2.5.5