From efd8a68e9c3c3a524417b1bf20aaf8791dd069aa 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/10] s3:smbXsrv.idl: add 8 byte channel_sequence number and request counters to IDL. Guenther Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison (cherry picked from commit 31f33a3f3996a5fff9833540c8227600f4aa2a55) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 d8a75dcc069126790c8b3e1d76261365f7331775 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Wed, 24 Feb 2016 15:51:14 +0100 Subject: [PATCH 02/10] smbd:smb2: add a modify flag to dispatch table This indicates that an operation is a modifying operation. Some parts of the upcoming channel sequence number logic only applies to modify operations. Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Michael Adam Signed-off-by: Stefan Metzmacher Reviewed-by: Jeremy Allison (cherry picked from commit 088468195b7f7f04eab0ce6fb928bda1c703e2fa) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 6b9830dd5649be65fcc8615e7cac4451c250f785 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 15 Mar 2016 12:36:59 +0100 Subject: [PATCH 03/10] smbd:smb2: add request_counters_updated to the smbd_smb2_request struct This will be used to keep track of whether the outstanding request counters have been updated in the dispatch, so that the reply code can act accordingly. Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison (cherry picked from commit ae6967ea3e39a1a5401be4a4c969b467dd22dce4) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- source3/smbd/globals.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 15f29a8..9e3e95c 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -727,6 +727,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 0f0732d4ac20ffc14e008291a67deaf529ae7f97 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Wed, 24 Feb 2016 15:54:41 +0100 Subject: [PATCH 04/10] smbd:smb2: implement channel sequence checks and request counters in dispatch Pair-Programmed-With: Stefan Metzmacher Pair-Programmed-With: Guenther Deschner Signed-off-by: Michael Adam Signed-off-by: Stefan Metzmacher Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison (cherry picked from commit 71d2b190646bdf5fce65a776dfe6873da8d82479) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 9be88c8fd8779813cbef961b84751b1a079a2081 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Tue, 23 Feb 2016 20:54:34 +0100 Subject: [PATCH 05/10] smbd:smb2: update outstanding request counters before sending a reply This is part of the channel sequence number treatment of multi-channel. Pair-Programmed-With: Guenther Deschner Signed-off-by: Michael Adam Signed-off-by: Guenther Deschner Reviewed-by: Jeremy Allison (cherry picked from commit 7dbb1707d96e39bed8898db08339d3b2d768c87c) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 ce5a7e80205c45fae7ec14606a683dd068ad1e7f Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Sat, 27 Feb 2016 14:02:02 +0100 Subject: [PATCH 06/10] smbd:smb2: add some asserts before decrementing the counters Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison (cherry picked from commit f81f3a2d78832258b09bcc63d5cce2b4594cbbc8) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 01b961d46884856414bf4117b4f5cf39786bed5e 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/10] 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 (cherry picked from commit ccda60ed9b33bb22ec2e162401a949aeaa631c8d) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 faf74ca..4332374 100644 --- a/libcli/smb/smbXcli_base.c +++ b/libcli/smb/smbXcli_base.c @@ -5522,6 +5522,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 8b9851b..16c8848 100644 --- a/libcli/smb/smbXcli_base.h +++ b/libcli/smb/smbXcli_base.h @@ -413,6 +413,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 ad29a33fe582618141a3e67bdc2981856fd67094 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/10] torture:smb2: add test for checking sequence number wrap around. Guenther Signed-off-by: Guenther Deschner Reviewed-by: Michael Adam Reviewed-by: Jeremy Allison (cherry picked from commit 2b799880b91f2ee44531644c62916f9a50531d04) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- selftest/knownfail | 1 + source4/torture/smb2/replay.c | 266 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) diff --git a/selftest/knownfail b/selftest/knownfail index 1678cd0..997d29c 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -211,6 +211,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/10] lib/torture: add torture_assert_u64_not_equal_goto macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guenther Signed-off-by: Günther Deschner Reviewed-by: Jeremy Allison (cherry picked from commit c5c3f91c6fd1ac3282d2fa27e262af097f0adfca) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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 75a45cbb1108fff094e7f9b442c6f49024efc65a Mon Sep 17 00:00:00 2001 From: Anubhav Rakshit Date: Thu, 30 Oct 2014 13:20:57 +0530 Subject: [PATCH 10/10] torture:smb2: Add test replay6 to verify Error Codes for DurableHandleReqV2 replay Pair-Programmed-With: Stefan Metzmacher 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 (cherry picked from commit e095a61c4b36e71b03d8afc724da09c91603a29b) BUG: https://bugzilla.samba.org/show_bug.cgi?id=11809 --- 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