From 79d69f53de29440def5d169ea90e423380b2f5c1 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Thu, 25 Aug 2022 09:54:52 +0200 Subject: [PATCH 1/7] smbXcli: Pass negotiate contexts through smbXcli_negprot_send/recv We already don't allow setting max_credits in the sync wrapper, so omit the contexts there as well. Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Fri Aug 26 19:54:03 UTC 2022 on sn-devel-184 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 (cherry picked from commit 4ddd277c0b77c502ed6b11e07c92c91f24ac9c15) --- libcli/smb/smbXcli_base.c | 80 +++++++++++++++++---- libcli/smb/smbXcli_base.h | 9 ++- source3/libsmb/cliconnect.c | 16 +++-- source3/torture/torture.c | 11 ++- source4/libcli/raw/rawnegotiate.c | 5 +- source4/libcli/smb2/connect.c | 5 +- source4/libcli/smb_composite/connect_nego.c | 5 +- 7 files changed, 101 insertions(+), 30 deletions(-) diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c index c28850ee57de..7db0600dd48b 100644 --- a/libcli/smb/smbXcli_base.c +++ b/libcli/smb/smbXcli_base.c @@ -4220,6 +4220,8 @@ static const struct { struct smbXcli_negprot_state { struct smbXcli_conn *conn; struct tevent_context *ev; + struct smb2_negotiate_contexts *in_ctx; + struct smb2_negotiate_contexts *out_ctx; uint32_t timeout_msec; struct { @@ -4242,7 +4244,8 @@ struct tevent_req *smbXcli_negprot_send(TALLOC_CTX *mem_ctx, uint32_t timeout_msec, enum protocol_types min_protocol, enum protocol_types max_protocol, - uint16_t max_credits) + uint16_t max_credits, + struct smb2_negotiate_contexts *in_ctx) { struct tevent_req *req, *subreq; struct smbXcli_negprot_state *state; @@ -4254,6 +4257,7 @@ struct tevent_req *smbXcli_negprot_send(TALLOC_CTX *mem_ctx, } state->conn = conn; state->ev = ev; + state->in_ctx = in_ctx; state->timeout_msec = timeout_msec; if (min_protocol == PROTOCOL_NONE) { @@ -4934,6 +4938,25 @@ static struct tevent_req *smbXcli_negprot_smb2_subreq(struct smbXcli_negprot_sta return NULL; } + if (state->in_ctx != NULL) { + struct smb2_negotiate_contexts *ctxs = state->in_ctx; + + for (i=0; inum_contexts; i++) { + struct smb2_negotiate_context *ctx = + &ctxs->contexts[i]; + + status = smb2_negotiate_context_add( + state, + &c, + ctx->type, + ctx->data.data, + ctx->data.length); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + } + } + status = smb2_negotiate_context_push(state, &b, c); if (!NT_STATUS_IS_OK(status)) { return NULL; @@ -4988,7 +5011,6 @@ static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) uint8_t *body; size_t i; uint16_t dialect_revision; - struct smb2_negotiate_contexts c = { .num_contexts = 0, }; uint32_t negotiate_context_offset = 0; uint16_t negotiate_context_count = 0; DATA_BLOB negotiate_context_blob = data_blob_null; @@ -5195,10 +5217,15 @@ static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) negotiate_context_blob.data += ctx_ofs; negotiate_context_blob.length -= ctx_ofs; - status = smb2_negotiate_context_parse(state, + state->out_ctx = talloc_zero(state, struct smb2_negotiate_contexts); + if (tevent_req_nomem(state->out_ctx, req)) { + return; + } + + status = smb2_negotiate_context_parse(state->out_ctx, negotiate_context_blob, negotiate_context_count, - &c); + state->out_ctx); if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { status = NT_STATUS_INVALID_NETWORK_RESPONSE; } @@ -5206,8 +5233,8 @@ static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) return; } - preauth = smb2_negotiate_context_find(&c, - SMB2_PREAUTH_INTEGRITY_CAPABILITIES); + preauth = smb2_negotiate_context_find( + state->out_ctx, SMB2_PREAUTH_INTEGRITY_CAPABILITIES); if (preauth == NULL) { tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); return; @@ -5237,7 +5264,8 @@ static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) return; } - sign_algo = smb2_negotiate_context_find(&c, SMB2_SIGNING_CAPABILITIES); + sign_algo = smb2_negotiate_context_find( + state->out_ctx, SMB2_SIGNING_CAPABILITIES); if (sign_algo != NULL) { const struct smb3_signing_capabilities *client_sign_algos = &state->conn->smb2.client.smb3_capabilities.signing; @@ -5296,7 +5324,8 @@ static void smbXcli_negprot_smb2_done(struct tevent_req *subreq) conn->smb2.server.sign_algo = sign_algo_selected; } - cipher = smb2_negotiate_context_find(&c, SMB2_ENCRYPTION_CAPABILITIES); + cipher = smb2_negotiate_context_find( + state->out_ctx, SMB2_ENCRYPTION_CAPABILITIES); if (cipher != NULL) { const struct smb3_encryption_capabilities *client_ciphers = &state->conn->smb2.client.smb3_capabilities.encryption; @@ -5516,9 +5545,26 @@ static NTSTATUS smbXcli_negprot_dispatch_incoming(struct smbXcli_conn *conn, return NT_STATUS_INVALID_NETWORK_RESPONSE; } -NTSTATUS smbXcli_negprot_recv(struct tevent_req *req) +NTSTATUS smbXcli_negprot_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts **out_ctx) { - return tevent_req_simple_recv_ntstatus(req); + struct smbXcli_negprot_state *state = tevent_req_data( + req, struct smbXcli_negprot_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + if (out_ctx != NULL) { + *out_ctx = talloc_move(mem_ctx, &state->out_ctx); + } + + tevent_req_received(req); + return NT_STATUS_OK; } NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, @@ -5543,9 +5589,15 @@ NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, if (ev == NULL) { goto fail; } - req = smbXcli_negprot_send(frame, ev, conn, timeout_msec, - min_protocol, max_protocol, - WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK); + req = smbXcli_negprot_send( + frame, + ev, + conn, + timeout_msec, + min_protocol, + max_protocol, + WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK, + NULL); if (req == NULL) { goto fail; } @@ -5553,7 +5605,7 @@ NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, if (!ok) { goto fail; } - status = smbXcli_negprot_recv(req); + status = smbXcli_negprot_recv(req, NULL, NULL); fail: TALLOC_FREE(frame); return status; diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h index 805a62ce3422..8e4fb81818fc 100644 --- a/libcli/smb/smbXcli_base.h +++ b/libcli/smb/smbXcli_base.h @@ -457,14 +457,19 @@ NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, NTSTATUS smb2cli_req_get_sent_iov(struct tevent_req *req, struct iovec *sent_iov); +struct smb2_negotiate_contexts; struct tevent_req *smbXcli_negprot_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct smbXcli_conn *conn, uint32_t timeout_msec, enum protocol_types min_protocol, enum protocol_types max_protocol, - uint16_t max_credits); -NTSTATUS smbXcli_negprot_recv(struct tevent_req *req); + uint16_t max_credits, + struct smb2_negotiate_contexts *in_ctx); +NTSTATUS smbXcli_negprot_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct smb2_negotiate_contexts **out_ctx); NTSTATUS smbXcli_negprot(struct smbXcli_conn *conn, uint32_t timeout_msec, enum protocol_types min_protocol, diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c index d5b48d347552..d2d048026e74 100644 --- a/source3/libsmb/cliconnect.c +++ b/source3/libsmb/cliconnect.c @@ -2850,11 +2850,15 @@ static void cli_start_connection_connected(struct tevent_req *subreq) return; } - subreq = smbXcli_negprot_send(state, state->ev, state->cli->conn, - state->cli->timeout, - state->min_protocol, - state->max_protocol, - WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK); + subreq = smbXcli_negprot_send( + state, + state->ev, + state->cli->conn, + state->cli->timeout, + state->min_protocol, + state->max_protocol, + WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK, + NULL); if (tevent_req_nomem(subreq, req)) { return; } @@ -2869,7 +2873,7 @@ static void cli_start_connection_done(struct tevent_req *subreq) req, struct cli_start_connection_state); NTSTATUS status; - status = smbXcli_negprot_recv(subreq); + status = smbXcli_negprot_recv(subreq, NULL, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; diff --git a/source3/torture/torture.c b/source3/torture/torture.c index cd32156ae427..fc139a4b808a 100644 --- a/source3/torture/torture.c +++ b/source3/torture/torture.c @@ -3953,8 +3953,15 @@ static bool run_negprot_nowait(int dummy) for (i=0;i<50000;i++) { struct tevent_req *req; - req = smbXcli_negprot_send(ev, ev, cli->conn, cli->timeout, - PROTOCOL_CORE, PROTOCOL_NT1, 0); + req = smbXcli_negprot_send( + ev, + ev, + cli->conn, + cli->timeout, + PROTOCOL_CORE, + PROTOCOL_NT1, + 0, + NULL); if (req == NULL) { TALLOC_FREE(ev); return false; diff --git a/source4/libcli/raw/rawnegotiate.c b/source4/libcli/raw/rawnegotiate.c index 51c6f0f9ecbe..6d1b73619329 100644 --- a/source4/libcli/raw/rawnegotiate.c +++ b/source4/libcli/raw/rawnegotiate.c @@ -106,7 +106,8 @@ struct tevent_req *smb_raw_negotiate_send(TALLOC_CTX *mem_ctx, timeout_msec, minprotocol, maxprotocol, - transport->options.max_credits); + transport->options.max_credits, + NULL); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } @@ -125,7 +126,7 @@ static void smb_raw_negotiate_done(struct tevent_req *subreq) struct smb_raw_negotiate_state); NTSTATUS status; - status = smbXcli_negprot_recv(subreq); + status = smbXcli_negprot_recv(subreq, NULL, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; diff --git a/source4/libcli/smb2/connect.c b/source4/libcli/smb2/connect.c index 9540704491ea..1f68d90538b2 100644 --- a/source4/libcli/smb2/connect.c +++ b/source4/libcli/smb2/connect.c @@ -187,7 +187,8 @@ static void smb2_connect_socket_done(struct composite_context *creq) state->transport->conn, timeout_msec, min_protocol, state->transport->options.max_protocol, - state->transport->options.max_credits); + state->transport->options.max_credits, + NULL); if (tevent_req_nomem(subreq, req)) { return; } @@ -203,7 +204,7 @@ static void smb2_connect_negprot_done(struct tevent_req *subreq) struct tevent_req); NTSTATUS status; - status = smbXcli_negprot_recv(subreq); + status = smbXcli_negprot_recv(subreq, NULL, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; diff --git a/source4/libcli/smb_composite/connect_nego.c b/source4/libcli/smb_composite/connect_nego.c index 3bd5dbc59e8e..7224dfa87946 100644 --- a/source4/libcli/smb_composite/connect_nego.c +++ b/source4/libcli/smb_composite/connect_nego.c @@ -167,7 +167,8 @@ static void smb_connect_nego_connect_done(struct composite_context *creq) timeout_msec, state->options.min_protocol, state->options.max_protocol, - state->options.max_credits); + state->options.max_credits, + NULL); if (tevent_req_nomem(subreq, req)) { return; } @@ -181,7 +182,7 @@ static void smb_connect_nego_nego_done(struct tevent_req *subreq) struct tevent_req); NTSTATUS status; - status = smbXcli_negprot_recv(subreq); + status = smbXcli_negprot_recv(subreq, NULL, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; -- 2.34.1 From ab9107cf83377b88683c8ece776622477f645fa2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 7 Aug 2023 11:03:41 +0200 Subject: [PATCH 2/7] s4:torture/smb2: let torture_smb2_con_sopt() use smb2_connect() There's no need for smb2_connect_ext(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider (cherry picked from commit ade663ee6ca1a2813b203ea667d933f4dab9e7b7) --- source4/torture/smb2/util.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c index ae6c14ebcbd2..01b1b2f0793a 100644 --- a/source4/torture/smb2/util.c +++ b/source4/torture/smb2/util.c @@ -475,19 +475,18 @@ bool torture_smb2_con_sopt(struct torture_context *tctx, return false; } - status = smb2_connect_ext(tctx, - host, - lpcfg_smb_ports(tctx->lp_ctx), - share, - lpcfg_resolve_context(tctx->lp_ctx), - samba_cmdline_get_creds(), - 0, - tree, - tctx->ev, - &options, - lpcfg_socket_options(tctx->lp_ctx), - lpcfg_gensec_settings(tctx, tctx->lp_ctx) - ); + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); if (!NT_STATUS_IS_OK(status)) { torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", host, share, nt_errstr(status)); -- 2.34.1 From 8528dc2423d6ab1c9716100849cd777c422aa8e0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 7 Aug 2023 11:03:41 +0200 Subject: [PATCH 3/7] s4:torture/smb2: let us have a common torture_smb2_con_share() BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider (cherry picked from commit dc5a500f0a76720b2a5cb5b1142cf4c35cb6bdea) --- source4/torture/smb2/acls.c | 34 --------------------------------- source4/torture/smb2/util.c | 29 +++++++++++++++++++--------- source4/torture/vfs/acl_xattr.c | 34 --------------------------------- 3 files changed, 20 insertions(+), 77 deletions(-) diff --git a/source4/torture/smb2/acls.c b/source4/torture/smb2/acls.c index a5df9da3b454..a27d4e079e67 100644 --- a/source4/torture/smb2/acls.c +++ b/source4/torture/smb2/acls.c @@ -2139,40 +2139,6 @@ done: } #endif -/** - * SMB2 connect with explicit share - **/ -static bool torture_smb2_con_share(struct torture_context *tctx, - const char *share, - struct smb2_tree **tree) -{ - struct smbcli_options options; - NTSTATUS status; - const char *host = torture_setting_string(tctx, "host", NULL); - - lpcfg_smbcli_options(tctx->lp_ctx, &options); - - status = smb2_connect_ext(tctx, - host, - lpcfg_smb_ports(tctx->lp_ctx), - share, - lpcfg_resolve_context(tctx->lp_ctx), - samba_cmdline_get_creds(), - 0, - tree, - tctx->ev, - &options, - lpcfg_socket_options(tctx->lp_ctx), - lpcfg_gensec_settings(tctx, tctx->lp_ctx) - ); - if (!NT_STATUS_IS_OK(status)) { - torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", - host, share, nt_errstr(status)); - return false; - } - return true; -} - static bool test_access_based(struct torture_context *tctx, struct smb2_tree *tree) { diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c index 01b1b2f0793a..ecf80d9105b1 100644 --- a/source4/torture/smb2/util.c +++ b/source4/torture/smb2/util.c @@ -459,22 +459,16 @@ bool torture_smb2_connection(struct torture_context *tctx, struct smb2_tree **tr /** * SMB2 connect with share from soption **/ -bool torture_smb2_con_sopt(struct torture_context *tctx, - const char *soption, - struct smb2_tree **tree) +bool torture_smb2_con_share(struct torture_context *tctx, + const char *share, + struct smb2_tree **tree) { struct smbcli_options options; NTSTATUS status; const char *host = torture_setting_string(tctx, "host", NULL); - const char *share = torture_setting_string(tctx, soption, NULL); lpcfg_smbcli_options(tctx->lp_ctx, &options); - if (share == NULL) { - torture_comment(tctx, "No share for option %s\n", soption); - return false; - } - status = smb2_connect(tctx, host, lpcfg_smb_ports(tctx->lp_ctx), @@ -495,6 +489,23 @@ bool torture_smb2_con_sopt(struct torture_context *tctx, return true; } +/** + * SMB2 connect with share from soption + **/ +bool torture_smb2_con_sopt(struct torture_context *tctx, + const char *soption, + struct smb2_tree **tree) +{ + const char *share = torture_setting_string(tctx, soption, NULL); + + if (share == NULL) { + torture_comment(tctx, "No share for option %s\n", soption); + return false; + } + + return torture_smb2_con_share(tctx, share, tree); +} + /* create and return a handle to a test file with a specific access mask diff --git a/source4/torture/vfs/acl_xattr.c b/source4/torture/vfs/acl_xattr.c index 46d8ead6cb03..1deb2b3b9984 100644 --- a/source4/torture/vfs/acl_xattr.c +++ b/source4/torture/vfs/acl_xattr.c @@ -47,40 +47,6 @@ } \ } while (0) -/** - * SMB2 connect with explicit share - **/ -static bool torture_smb2_con_share(struct torture_context *tctx, - const char *share, - struct smb2_tree **tree) -{ - struct smbcli_options options; - NTSTATUS status; - const char *host = torture_setting_string(tctx, "host", NULL); - - lpcfg_smbcli_options(tctx->lp_ctx, &options); - - status = smb2_connect_ext(tctx, - host, - lpcfg_smb_ports(tctx->lp_ctx), - share, - lpcfg_resolve_context(tctx->lp_ctx), - samba_cmdline_get_creds(), - 0, - tree, - tctx->ev, - &options, - lpcfg_socket_options(tctx->lp_ctx), - lpcfg_gensec_settings(tctx, tctx->lp_ctx) - ); - if (!NT_STATUS_IS_OK(status)) { - printf("Failed to connect to SMB2 share \\\\%s\\%s - %s\n", - host, share, nt_errstr(status)); - return false; - } - return true; -} - static bool test_default_acl_posix(struct torture_context *tctx, struct smb2_tree *tree_unused) { -- 2.34.1 From fdaa169d78f469578c8a082d8a726e19717ab76f Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 7 Aug 2023 12:22:43 +0200 Subject: [PATCH 4/7] s4:torture/smb2: make it possible to pass existing_conn to smb2_connect_ext() BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider (cherry picked from commit 2b93058be3f6e5eaee239ad3b0e707c62089d18e) --- source4/libcli/smb2/connect.c | 4 +++- source4/torture/smb2/util.c | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source4/libcli/smb2/connect.c b/source4/libcli/smb2/connect.c index 1f68d90538b2..64b678654468 100644 --- a/source4/libcli/smb2/connect.c +++ b/source4/libcli/smb2/connect.c @@ -405,6 +405,7 @@ NTSTATUS smb2_connect_ext(TALLOC_CTX *mem_ctx, const char *share, struct resolve_context *resolve_ctx, struct cli_credentials *credentials, + struct smbXcli_conn **existing_conn, uint64_t previous_session_id, struct smb2_tree **tree, struct tevent_context *ev, @@ -429,7 +430,7 @@ NTSTATUS smb2_connect_ext(TALLOC_CTX *mem_ctx, resolve_ctx, credentials, false, /* fallback_to_anonymous */ - NULL, /* existing_conn */ + existing_conn, previous_session_id, options, socket_options, @@ -473,6 +474,7 @@ NTSTATUS smb2_connect(TALLOC_CTX *mem_ctx, status = smb2_connect_ext(mem_ctx, host, ports, share, resolve_ctx, credentials, + NULL, /* existing_conn */ 0, /* previous_session_id */ tree, ev, options, socket_options, gensec_settings); diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c index ecf80d9105b1..233f589c73f6 100644 --- a/source4/torture/smb2/util.c +++ b/source4/torture/smb2/util.c @@ -426,6 +426,7 @@ bool torture_smb2_connection_ext(struct torture_context *tctx, share, lpcfg_resolve_context(tctx->lp_ctx), samba_cmdline_get_creds(), + NULL, /* existing_conn */ previous_session_id, tree, tctx->ev, -- 2.34.1 From 68beb2e190940f1c65d32ef72361ea6bf9bb30eb Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 4 Aug 2023 17:16:14 +0200 Subject: [PATCH 5/7] s4:torture/smb2: add smb2.multichannel.bugs.bug_15346 This demonstrates the race quite easily against Samba and works fine against Windows Server 2022. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider (cherry picked from commit 4028d6582907cf582730ceec56872d8584ad02e6) --- selftest/knownfail.d/samba3.smb2.multichannel | 1 + source4/torture/smb2/multichannel.c | 315 ++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 selftest/knownfail.d/samba3.smb2.multichannel diff --git a/selftest/knownfail.d/samba3.smb2.multichannel b/selftest/knownfail.d/samba3.smb2.multichannel new file mode 100644 index 000000000000..26b80a39d5be --- /dev/null +++ b/selftest/knownfail.d/samba3.smb2.multichannel @@ -0,0 +1 @@ +^samba3.smb2.multichannel.bugs.bug_15346 diff --git a/source4/torture/smb2/multichannel.c b/source4/torture/smb2/multichannel.c index 24c736d6701a..7c3a60c00162 100644 --- a/source4/torture/smb2/multichannel.c +++ b/source4/torture/smb2/multichannel.c @@ -31,6 +31,7 @@ #include "lib/cmdline/cmdline.h" #include "libcli/security/security.h" #include "libcli/resolve/resolve.h" +#include "lib/socket/socket.h" #include "lib/param/param.h" #include "lib/events/events.h" #include "oplock_break_handler.h" @@ -2345,6 +2346,315 @@ done: return ret; } +/* + * Test channel merging race + * This is a regression test for + * https://bugzilla.samba.org/show_bug.cgi?id=15346 + */ +struct test_multichannel_bug_15346_conn; + +struct test_multichannel_bug_15346_state { + struct torture_context *tctx; + struct test_multichannel_bug_15346_conn *conns; + size_t num_conns; + size_t num_ready; + bool asserted; + bool looping; +}; + +struct test_multichannel_bug_15346_conn { + struct test_multichannel_bug_15346_state *state; + size_t idx; + struct smbXcli_conn *smbXcli; + struct tevent_req *nreq; + struct tevent_req *ereq; +}; + +static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq); +static void test_multichannel_bug_15346_edone(struct tevent_req *subreq); + +static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq) +{ + struct test_multichannel_bug_15346_conn *conn = + (struct test_multichannel_bug_15346_conn *) + tevent_req_callback_data_void(subreq); + struct test_multichannel_bug_15346_state *state = conn->state; + struct torture_context *tctx = state->tctx; + NTSTATUS status; + bool ok = false; + + SMB_ASSERT(conn->nreq == subreq); + conn->nreq = NULL; + + status = smbXcli_negprot_recv(subreq, NULL, NULL); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted, + "smbXcli_negprot_recv failed"); + + torture_comment(tctx, "conn[%zu]: negprot done\n", conn->idx); + + conn->ereq = smb2cli_echo_send(conn->smbXcli, + tctx->ev, + conn->smbXcli, + state->num_conns * 2 * 1000); + torture_assert_goto(tctx, conn->ereq != NULL, ok, asserted, + "smb2cli_echo_send"); + tevent_req_set_callback(conn->ereq, + test_multichannel_bug_15346_edone, + conn); + + return; + +asserted: + SMB_ASSERT(!ok); + state->asserted = true; + state->looping = false; + return; +} + +static void test_multichannel_bug_15346_edone(struct tevent_req *subreq) +{ + struct test_multichannel_bug_15346_conn *conn = + (struct test_multichannel_bug_15346_conn *) + tevent_req_callback_data_void(subreq); + struct test_multichannel_bug_15346_state *state = conn->state; + struct torture_context *tctx = state->tctx; + NTSTATUS status; + bool ok = false; + + SMB_ASSERT(conn->ereq == subreq); + conn->ereq = NULL; + + status = smb2cli_echo_recv(subreq); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted, + "smb2cli_echo_recv failed"); + + torture_comment(tctx, "conn[%zu]: echo done\n", conn->idx); + + state->num_ready += 1; + if (state->num_ready < state->num_conns) { + return; + } + + state->looping = false; + return; + +asserted: + SMB_ASSERT(!ok); + state->asserted = true; + state->looping = false; + return; +} + +static bool test_multichannel_bug_15346(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 resolve_context *resolve_ctx = lpcfg_resolve_context(tctx->lp_ctx); + const char *socket_options = lpcfg_socket_options(tctx->lp_ctx); + struct gensec_settings *gsettings = NULL; + bool ret = true; + NTSTATUS status; + struct smb2_transport *transport1 = tree1->session->transport; + struct test_multichannel_bug_15346_state *state = NULL; + uint32_t server_capabilities; + struct smb2_handle root_handle = {{0}}; + size_t i; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_fail(tctx, + "SMB 3.X Dialect family required for Multichannel" + " tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_fail(tctx, + "Server does not support multichannel."); + } + + torture_comment(tctx, "Testing for BUG 15346\n"); + + state = talloc_zero(tctx, struct test_multichannel_bug_15346_state); + torture_assert_goto(tctx, state != NULL, ret, done, + "talloc_zero"); + state->tctx = tctx; + + gsettings = lpcfg_gensec_settings(state, tctx->lp_ctx); + torture_assert_goto(tctx, gsettings != NULL, ret, done, + "lpcfg_gensec_settings"); + + /* + * 32 is the W2K12R2 and W2K16 limit + * add 31 additional connections + */ + state->num_conns = 31; + state->conns = talloc_zero_array(state, + struct test_multichannel_bug_15346_conn, + state->num_conns); + torture_assert_goto(tctx, state->conns != NULL, ret, done, + "talloc_zero_array"); + + /* + * First we open additional the tcp connections + */ + + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + struct socket_context *sock = NULL; + uint16_t port = 445; + struct smbcli_options options = transport1->options; + + conn->state = state; + conn->idx = i; + + status = socket_connect_multi(state->conns, + host, + 1, &port, + resolve_ctx, + tctx->ev, + &sock, + &port); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "socket_connect_multi failed"); + + conn->smbXcli = smbXcli_conn_create(state->conns, + sock->fd, + host, + SMB_SIGNING_OFF, + 0, + &options.client_guid, + options.smb2_capabilities, + &options.smb3_capabilities); + torture_assert_goto(tctx, conn->smbXcli != NULL, ret, done, + "smbXcli_conn_create failed"); + sock->fd = -1; + TALLOC_FREE(sock); + } + + /* + * Now prepare the async SMB2 Negotiate requests + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + + conn->nreq = smbXcli_negprot_send(conn->smbXcli, + tctx->ev, + conn->smbXcli, + state->num_conns * 2 * 1000, + smbXcli_conn_protocol(transport1->conn), + smbXcli_conn_protocol(transport1->conn), + 33, /* max_credits */ + NULL); + torture_assert_goto(tctx, conn->nreq != NULL, ret, done, "smbXcli_negprot_send"); + tevent_req_set_callback(conn->nreq, + test_multichannel_bug_15346_ndone, + conn); + } + + /* + * now we loop until all negprot and the first round + * of echos are done. + */ + state->looping = true; + while (state->looping) { + torture_assert_goto(tctx, tevent_loop_once(tctx->ev) == 0, + ret, done, "tevent_loop_once"); + } + + if (state->asserted) { + ret = false; + goto done; + } + + /* + * No we check that the connections are still usable + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + + torture_comment(tctx, "conn[%zu]: checking echo again1\n", conn->idx); + + status = smb2cli_echo(conn->smbXcli, 1000); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2cli_echo failed"); + } + + status = smb2_util_roothandle(tree1, &root_handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_roothandle failed"); + + /* + * No we check that the connections are still usable + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + struct smbcli_options options = transport1->options; + struct smb2_session *session = NULL; + struct smb2_tree *tree = NULL; + union smb_fileinfo io; + + torture_comment(tctx, "conn[%zu]: checking session bind\n", conn->idx); + + /* + * Prepare smb2_{tree,session,transport} structures + * for the existing connection. + */ + options.only_negprot = true; + status = smb2_connect_ext(state->conns, + host, + NULL, /* ports */ + share, + resolve_ctx, + samba_cmdline_get_creds(), + &conn->smbXcli, + 0, /* previous_session_id */ + &tree, + tctx->ev, + &options, + socket_options, + gsettings); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect_ext failed"); + conn->smbXcli = tree->session->transport->conn; + + session = smb2_session_channel(tree->session->transport, + lpcfg_gensec_settings(tree, tctx->lp_ctx), + tree, + tree1->session); + torture_assert_goto(tctx, session != NULL, ret, done, + "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* + * Fix up the bound smb2_tree + */ + tree->session = session; + tree->smbXcli = tree1->smbXcli; + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = root_handle; + + status = smb2_getinfo_file(tree, tree, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + } + + done: + talloc_free(state); + + return ret; +} + struct torture_suite *torture_smb2_multichannel_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "multichannel"); @@ -2354,10 +2664,13 @@ struct torture_suite *torture_smb2_multichannel_init(TALLOC_CTX *ctx) "oplocks"); struct torture_suite *suite_leases = torture_suite_create(ctx, "leases"); + struct torture_suite *suite_bugs = torture_suite_create(ctx, + "bugs"); torture_suite_add_suite(suite, suite_generic); torture_suite_add_suite(suite, suite_oplocks); torture_suite_add_suite(suite, suite_leases); + torture_suite_add_suite(suite, suite_bugs); torture_suite_add_1smb2_test(suite_generic, "interface_info", test_multichannel_interface_info); @@ -2379,6 +2692,8 @@ struct torture_suite *torture_smb2_multichannel_init(TALLOC_CTX *ctx) test_multichannel_lease_break_test3); torture_suite_add_1smb2_test(suite_leases, "test4", test_multichannel_lease_break_test4); + torture_suite_add_1smb2_test(suite_bugs, "bug_15346", + test_multichannel_bug_15346); suite->description = talloc_strdup(suite, "SMB2 Multichannel tests"); -- 2.34.1 From ac73fbbc6d8e82a26c13b36c5470005157c43391 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 3 Aug 2023 15:34:29 +0200 Subject: [PATCH 6/7] s3:smbd: always clear filter_subreq in smb2srv_client_mc_negprot_next() Commit 5d66d5b84f87267243dcd5223210906ce589af91 introduced a 'verify_again:' target, if we ever hit that, we would leak the existing filter_subreq. Moving it just above a possible messaging_filtered_read_send() will allow us to only clear it if we actually create a new request. That will help us in the next commits. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider (cherry picked from commit 50d61e5300250922bf36bb699306f82dff6a00b9) --- source3/smbd/smbXsrv_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/smbd/smbXsrv_client.c b/source3/smbd/smbXsrv_client.c index f57bc724910d..4d3fe30f812b 100644 --- a/source3/smbd/smbXsrv_client.c +++ b/source3/smbd/smbXsrv_client.c @@ -554,7 +554,6 @@ static void smb2srv_client_mc_negprot_next(struct tevent_req *req) uint32_t seqnum = 0; struct server_id last_server_id = { .pid = 0, }; - TALLOC_FREE(state->filter_subreq); SMB_ASSERT(state->db_rec == NULL); state->db_rec = smbXsrv_client_global_fetch_locked(table->global.db_ctx, &client_guid, @@ -635,6 +634,7 @@ verify_again: SMB_ASSERT(last_server_id.pid == 0); last_server_id = global->server_id; + TALLOC_FREE(state->filter_subreq); if (procid_is_local(&global->server_id)) { subreq = messaging_filtered_read_send(state, state->ev, -- 2.34.1 From 9ffe950dc76aee455d42981733769dacf2166246 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 3 Aug 2023 15:45:45 +0200 Subject: [PATCH 7/7] s3:smbd: fix multichannel connection passing race If a client opens multiple connection with the same client guid in parallel, our connection passing is likely to hit a race. Assume we have 3 processes: smbdA: This process already handles all connections for a given client guid smbdB: This just received a new connection with an SMB2 neprot for the same client guid smbdC: This also received a new connection with an SMB2 neprot for the same client guid Now both smbdB and smbdC send a MSG_SMBXSRV_CONNECTION_PASS message to smbdA. These messages contain the socket fd for each connection. While waiting for a MSG_SMBXSRV_CONNECTION_PASSED message from smbdA, both smbdB and smbdC watch the smbXcli_client.tdb record for changes (that also verifies smbdA stays alive). Once one of them say smbdB received the MSG_SMBXSRV_CONNECTION_PASSED message, the dbwrap_watch logic will wakeup smbdC in order to let it recheck the smbXcli_client.tdb record in order to handle the case where smbdA died or deleted its record. Now smbdC rechecks the smbXcli_client.tdb record, but it was not woken because of a problem with smbdA. It meant that smbdC sends a MSG_SMBXSRV_CONNECTION_PASS message including the socket fd again. As a result smbdA got the socket fd from smbdC twice (or even more), and creates two (or more) smbXsrv_connection structures for the same low level tcp connection. And it also sends more than one SMB2 negprot response. Depending on the tevent logic, it will use different smbXsrv_connection structures to process incoming requests. And this will almost immediately result in errors. The typicall error is: smb2_validate_sequence_number: smb2_validate_sequence_number: bad message_id 2 (sequence id 2) (granted = 1, low = 1, range = 1) But other errors would also be possible. The detail that leads to the long delays on the client side is that our smbd_server_connection_terminate_ex() code will close only the fd of a single smbXsrv_connection, but the refcount on the socket fd in the kernel is still not 0, so the tcp connection is still alive... Now we remember the server_id of the process that we send the MSG_SMBXSRV_CONNECTION_PASS message to. And just keep watching the smbXcli_client.tdb record if the server_id don't change. As we just need more patience to wait for the MSG_SMBXSRV_CONNECTION_PASSED message. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15346 Signed-off-by: Stefan Metzmacher Reviewed-by: Andreas Schneider Autobuild-User(master): Andreas Schneider Autobuild-Date(master): Tue Aug 8 13:59:58 UTC 2023 on atb-devel-224 (cherry picked from commit f348b84fbcf203ab1ba92840cf7aecd55dbf9aa0) --- selftest/knownfail.d/samba3.smb2.multichannel | 1 - source3/smbd/smbXsrv_client.c | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) delete mode 100644 selftest/knownfail.d/samba3.smb2.multichannel diff --git a/selftest/knownfail.d/samba3.smb2.multichannel b/selftest/knownfail.d/samba3.smb2.multichannel deleted file mode 100644 index 26b80a39d5be..000000000000 --- a/selftest/knownfail.d/samba3.smb2.multichannel +++ /dev/null @@ -1 +0,0 @@ -^samba3.smb2.multichannel.bugs.bug_15346 diff --git a/source3/smbd/smbXsrv_client.c b/source3/smbd/smbXsrv_client.c index 4d3fe30f812b..90322c5e60ab 100644 --- a/source3/smbd/smbXsrv_client.c +++ b/source3/smbd/smbXsrv_client.c @@ -487,6 +487,7 @@ struct smb2srv_client_mc_negprot_state { struct tevent_context *ev; struct smbd_smb2_request *smb2req; struct db_record *db_rec; + struct server_id sent_server_id; uint64_t watch_instance; uint32_t last_seqnum; struct tevent_req *filter_subreq; @@ -529,6 +530,8 @@ struct tevent_req *smb2srv_client_mc_negprot_send(TALLOC_CTX *mem_ctx, tevent_req_set_cleanup_fn(req, smb2srv_client_mc_negprot_cleanup); + server_id_set_disconnected(&state->sent_server_id); + smb2srv_client_mc_negprot_next(req); if (!tevent_req_is_in_progress(req)) { @@ -624,6 +627,30 @@ verify_again: return; } + if (server_id_equal(&state->sent_server_id, &global->server_id)) { + /* + * We hit a race with other concurrent connections, + * which have woken us. + * + * We already sent the pass or drop message to + * the process, so we need to wait for a + * response and not pass the connection + * again! Otherwise the process would + * receive the same tcp connection via + * more than one file descriptor and + * create more than one smbXsrv_connection + * structure for the same tcp connection, + * which means the client would see more + * than one SMB2 negprot response to its + * single SMB2 netprot request and we + * as server get the session keys and + * message id validation wrong + */ + goto watch_again; + } + + server_id_set_disconnected(&state->sent_server_id); + /* * If last_server_id is set, we expect * smbXsrv_client_global_verify_record() @@ -659,6 +686,7 @@ verify_again: */ goto verify_again; } + state->sent_server_id = global->server_id; if (tevent_req_nterror(req, status)) { return; } @@ -673,11 +701,14 @@ verify_again: */ goto verify_again; } + state->sent_server_id = global->server_id; if (tevent_req_nterror(req, status)) { return; } } +watch_again: + /* * If the record changed, but we are not happy with the change yet, * we better remove ourself from the waiter list -- 2.34.1