diff --git a/source3/smbd/smb2_sesssetup.c b/source3/smbd/smb2_sesssetup.c index edef0cc..7d1066e 100644 --- a/source3/smbd/smb2_sesssetup.c +++ b/source3/smbd/smb2_sesssetup.c @@ -455,10 +455,31 @@ static int pp_self_ref_destructor(struct smbd_smb2_session_setup_state **pp_stat return 0; } -static int smbd_smb2_session_setup_state_destructor(struct smbd_smb2_session_setup_state *state) +static void remove_outstanding_session_refs(struct smbd_smb2_request *smb2req) { struct smbd_smb2_request *preq; + for (preq = smb2req->sconn->smb2.requests; preq != NULL; preq = preq->next) { + if (preq == smb2req) { + /* Don't remove the session from the current + request in flight. */ + continue; + } + if (preq->session == smb2req->session) { + preq->session = NULL; + /* + * If we no longer have a session we can't + * sign or encrypt replies. + */ + preq->do_signing = false; + preq->do_encryption = false; + } + } + +} + +static int smbd_smb2_session_setup_state_destructor(struct smbd_smb2_session_setup_state *state) +{ /* * If state->session is not NULL, * we move the session from the session table to the request on failure @@ -479,21 +500,7 @@ static int smbd_smb2_session_setup_state_destructor(struct smbd_smb2_session_set * to it. */ - for (preq = state->smb2req->sconn->smb2.requests; preq != NULL; preq = preq->next) { - if (preq == state->smb2req) { - continue; - } - if (preq->session == state->smb2req->session) { - preq->session = NULL; - /* - * If we no longer have a session we can't - * sign or encrypt replies. - */ - preq->do_signing = false; - preq->do_encryption = false; - } - } - + remove_outstanding_session_refs(state->smb2req); return 0; } @@ -788,44 +795,199 @@ static NTSTATUS smbd_smb2_session_setup_recv(struct tevent_req *req, return status; } +static bool any_outstanding_session_requests(const struct smbd_smb2_request *smb2req, + bool do_cancel) +{ + bool ret = false; + struct smbd_smb2_request *preq; + + if (smb2req->session == NULL) { + return false; + } + for (preq = smb2req->sconn->smb2.requests; preq != NULL; preq = preq->next) { + if (preq == smb2req) { + continue; + } + if (preq->session == smb2req->session) { + if (do_cancel) { + tevent_req_cancel(preq->subreq); + } + ret = true; + } + } + return ret; +} + +static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req); +static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req); +static void smbd_smb2_request_logoff_done(struct tevent_req *subreq); + NTSTATUS smbd_smb2_request_process_logoff(struct smbd_smb2_request *req) { NTSTATUS status; - DATA_BLOB outbody; + struct tevent_req *subreq; status = smbd_smb2_request_verify_sizes(req, 0x04); if (!NT_STATUS_IS_OK(status)) { return smbd_smb2_request_error(req, status); } + subreq = smbd_smb2_logoff_send(req, req->sconn->ev_ctx, req); + if (subreq == NULL) { + return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); + } + tevent_req_set_callback(subreq, smbd_smb2_request_logoff_done, req); + + return smbd_smb2_request_pending_queue(req, subreq, 500); +} + +struct smbd_smb2_logout_state { + struct timeval end_logout; +}; + +static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req) +{ + NTSTATUS status; + struct tevent_req *req; + struct smbd_smb2_logout_state *state; /* dummy state. */ + + req = smb2req->subreq; + if (req == NULL) { + /* New logoff call. */ + req = tevent_req_create(mem_ctx, &state, struct smbd_smb2_logout_state); + if (req == NULL) { + return NULL; + } + + /* Don't wait more than 5 seconds for cancel. */ + state->end_logout = tevent_timeval_current_ofs(5, 0); + + if (any_outstanding_session_requests(smb2req, true)) { + /* We have outstanding requests. */ + struct tevent_immediate *im = tevent_create_immediate(smb2req); + if (!im) { + return NULL; + } + /* Try again immediately - we just requested they be cancelled. */ + tevent_schedule_immediate(im, + smb2req->sconn->ev_ctx, + smbd_smb2_request_dispatch_immediate, + smb2req); + return req; + } + } else { + /* Re-entrant logoff call. */ + struct timeval curr = tevent_timeval_current(); + state = tevent_req_data(req, struct smbd_smb2_logout_state); + + if (tevent_timeval_compare(&state->end_logout, &curr) >= 0) { + /* Logic error - terminate.. */ + abort(); + } + + if (any_outstanding_session_requests(smb2req, false)) { + /* We are still waiting for the cancellation of + requests. */ + struct tevent_immediate *im = tevent_create_immediate(smb2req); + if (!im) { + return NULL; + } + /* Try again later. */ + tevent_schedule_immediate(im, + smb2req->sconn->ev_ctx, + smbd_smb2_request_dispatch_immediate, + smb2req); + return req; + } + } + /* - * TODO: cancel all outstanding requests on the session + * When we get here, we are actually going to tear + * down the sesssion pointer. */ - status = smbXsrv_session_logoff(req->session); - if (!NT_STATUS_IS_OK(status)) { + status = smbXsrv_session_logoff(smb2req->session); + if (tevent_req_nterror(req, status)) { DEBUG(0, ("smbd_smb2_request_process_logoff: " "smbXsrv_session_logoff() failed: %s\n", nt_errstr(status))); - /* - * If we hit this case, there is something completely - * wrong, so we better disconnect the transport connection. - */ - return status; - } + return tevent_req_post(req, ev); + } /* * we may need to sign the response, so we need to keep * the session until the response is sent to the wire. */ - talloc_steal(req, req->session); + talloc_steal(smb2req, smb2req->session); - outbody = data_blob_talloc(req->out.vector, NULL, 0x04); + /* + * The return from smbXsrv_session_logoff() + * leaves all outstanding pointers to + * smb2req->session not on this request in an + * undefined state, ensure we don't try and + * access any req->session pointers once + * we return. + */ + remove_outstanding_session_refs(smb2req); + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void smbd_smb2_request_logoff_done(struct tevent_req *subreq) +{ + struct smbd_smb2_request *smb2req = + tevent_req_callback_data(subreq, + struct smbd_smb2_request); + DATA_BLOB outbody; + NTSTATUS status; + NTSTATUS error; + + status = smbd_smb2_logoff_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + error = smbd_smb2_request_error(smb2req, status); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->sconn, + nt_errstr(error)); + return; + } + return; + } + + outbody = data_blob_talloc(smb2req->out.vector, NULL, 0x04); if (outbody.data == NULL) { - return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); + error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->sconn, + nt_errstr(error)); + return; + } + return; } SSVAL(outbody.data, 0x00, 0x04); /* struct size */ SSVAL(outbody.data, 0x02, 0); /* reserved */ - return smbd_smb2_request_done(req, outbody, NULL); + error = smbd_smb2_request_done(smb2req, outbody, NULL); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->sconn, + nt_errstr(error)); + return; + } +} + +static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + tevent_req_received(req); + return NT_STATUS_OK; } diff --git a/source3/smbd/smb2_tcon.c b/source3/smbd/smb2_tcon.c index ca67461..1533b5d 100644 --- a/source3/smbd/smb2_tcon.c +++ b/source3/smbd/smb2_tcon.c @@ -411,6 +411,26 @@ static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req, return NT_STATUS_OK; } +static bool cancel_outstanding_tcon_requests(const struct smbd_smb2_request *smb2req) +{ + bool ret = false; + struct smbd_smb2_request *preq; + + if (smb2req->tcon == NULL) { + return false; + } + for (preq = smb2req->sconn->smb2.requests; preq != NULL; preq = preq->next) { + if (preq == smb2req) { + continue; + } + if (preq->tcon == smb2req->tcon) { + tevent_req_cancel(preq->subreq); + ret = true; + } + } + return ret; +} + NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req) { NTSTATUS status; @@ -421,9 +441,47 @@ NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req) return smbd_smb2_request_error(req, status); } - /* - * TODO: cancel all outstanding requests on the tcon - */ + if (req->current_idx == 1) { + /* + * If we have a singleton request, or we're + * at the start of a compound request, + * try and cancel all outstanding requests + * on this tcon and then re-process this + * request. + * + * If the TDis is not at the start + * of a compound sequence then just treat it + * synchronously and rely on the tcon + * lookup smbd_smb2_request_check_tcon() + * to prevent crashes on susbsequently + * queued requests on this tcon. It's too hard + * at the moment to get this right. + * + * TODO: for a TDis not at the start + * of a compound sequence we should send the + * previous reply data using something like + * smb2_send_async_interim_response(), then + * spin off the rest of the compound requests + * into a new request where TDis is + * the start of the compound. We can then + * treat as a smbd_smb2_request_dispatch_immediate(), + * and reschedule as normal. JRA. + */ + if (cancel_outstanding_tcon_requests(req)) { + /* We have outstanding requests. */ + struct tevent_immediate *im = tevent_create_immediate(req); + if (!im) { + return NT_STATUS_NO_MEMORY; + } + tevent_schedule_immediate(im, + req->sconn->ev_ctx, + smbd_smb2_request_dispatch_immediate, + req); + /* Try again later. */ + return NT_STATUS_OK; + } + } + status = smbXsrv_tcon_disconnect(req->tcon, req->tcon->compat->vuid); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("smbd_smb2_request_process_tdis: " diff --git a/source4/torture/smb2/notify.c b/source4/torture/smb2/notify.c index e83b099..1e52635 100644 --- a/source4/torture/smb2/notify.c +++ b/source4/torture/smb2/notify.c @@ -69,6 +69,13 @@ goto done; \ }} while (0) +#define WAIT_FOR_ASYNC_RESPONSE(req) \ + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once(torture->ev) != 0) { \ + break; \ + } \ + } + #define BASEDIR "test_notify" #define FNAME "smb2-notify01.dat" @@ -1980,12 +1987,89 @@ done: } /* + basic testing of change notifies followed by a cancel on the change notify, + followed by a ulogoff +*/ + +static bool torture_smb2_notify_cancel_ulogoff(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, + "TESTING CHANGE NOTIFY FOLLOWED BY CANCEL + ULOGOFF\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree2, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + + WAIT_FOR_ASYNC_RESPONSE(req); + +// sleep(2); + +// smb2_cancel(req); + + status = smb2_logoff(tree2->session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree1, BASEDIR); + return ret; +} + + +/* basic testing of SMB2 change notify */ struct torture_suite *torture_smb2_notify_init(void) { struct torture_suite *suite = torture_suite_create(talloc_autofree_context(), "notify"); + torture_suite_add_1smb2_test(suite, "cancel+logoff", torture_smb2_notify_cancel_ulogoff); torture_suite_add_1smb2_test(suite, "valid-req", test_valid_request); torture_suite_add_1smb2_test(suite, "tcon", torture_smb2_notify_tcon); torture_suite_add_2smb2_test(suite, "dir", torture_smb2_notify_dir);