]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
smbtorture: add test "open-brlock-deadlock"
authorRalph Boehme <slow@samba.org>
Thu, 9 Jan 2025 11:27:43 +0000 (12:27 +0100)
committerJule Anger <janger@samba.org>
Thu, 17 Apr 2025 11:31:14 +0000 (11:31 +0000)
smbtorture reproducer for bug 15767. As it needs a very specific setup that
can't easily be done in selftest, the test is only executed when manually called
with

  --option=torture:open_brlock_deadlock_timemout=SEC

To prepare the setup for the test set:

  tdb_hash_size:locking.tdb = 1
  tdb_hash_size:brlock.tdb = 1

and remove both tdb from disk which is needed so the TDBs get recreated with the
new hash_size.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767

Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 7c60498cee7dca5770d4d1f623c472d585ae9cae)

source4/torture/smb2/lock.c

index eac0d557fc38659f47a591623bec1e08b26aad8d..e5cf61a471abd5c9bdfe71afe7d4b99d3d5e66ac 100644 (file)
@@ -3465,6 +3465,288 @@ done:
        return ret;
 }
 
+struct open_brlock_deadlock_state {
+       bool stop;
+       bool ok;
+       struct torture_context *tctx;
+       struct smb2_tree *tree1;
+       struct smb2_tree *tree2;
+       struct smb2_create cr;
+       struct smb2_request *cr_req;
+       struct smb2_close cl;
+       struct smb2_request *cl_req;
+
+       struct smb2_lock_element el;
+       struct smb2_lock lock;
+       struct smb2_request *lock_req;
+};
+
+static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_open(
+       struct open_brlock_deadlock_state *state)
+{
+       if (state->stop) {
+               return;
+       }
+
+       state->cr_req = smb2_create_send(state->tree1, &state->cr);
+       torture_assert_goto(state->tctx, state->cr_req != NULL, state->ok, failed,
+                           "smb2_create_send failed\n");
+
+       state->cr_req->async.fn = test_open_brlock_deadlock_loop_opened;
+       state->cr_req->async.private_data = state;
+       return;
+failed:
+       state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_close(
+       struct open_brlock_deadlock_state *state);
+
+static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req)
+{
+       struct open_brlock_deadlock_state *state = req->async.private_data;
+       TALLOC_CTX *frame = talloc_stackframe();
+       NTSTATUS status;
+
+       status = smb2_create_recv(req, frame, &state->cr);
+       torture_assert_ntstatus_ok_goto(state->tctx, status,
+                                       state->ok, failed, __location__);
+       state->cr_req = NULL;
+
+       TALLOC_FREE(frame);
+       test_open_brlock_deadlock_loop_close(state);
+       return;
+
+failed:
+       state->stop = true;
+       TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_close(
+       struct open_brlock_deadlock_state *state)
+{
+       if (state->stop) {
+               return;
+       }
+
+       state->cl.in.file = state->cr.out.file;
+       state->cl_req = smb2_close_send(state->tree1, &state->cl);
+       torture_assert_goto(state->tctx, state->cl_req != NULL, state->ok, failed,
+                           "smb2_create_send failed\n");
+
+       state->cl_req->async.fn = test_open_brlock_deadlock_loop_closed;
+       state->cl_req->async.private_data = state;
+       return;
+failed:
+       state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req)
+{
+       struct open_brlock_deadlock_state *state = req->async.private_data;
+       TALLOC_CTX *frame = talloc_stackframe();
+       NTSTATUS status;
+
+       status = smb2_close_recv(req, &state->cl);
+       torture_assert_ntstatus_ok_goto(state->tctx, status,
+                                       state->ok, failed, __location__);
+       state->cl_req = NULL;
+
+       TALLOC_FREE(frame);
+       test_open_brlock_deadlock_loop_open(state);
+       return;
+
+failed:
+       state->stop = true;
+       TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_lock(
+       struct open_brlock_deadlock_state *state)
+{
+       if (state->stop) {
+               return;
+       }
+
+       state->el.flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+
+       state->lock_req = smb2_lock_send(state->tree2, &state->lock);
+       torture_assert_goto(state->tctx, state->lock_req != NULL,
+                           state->ok, failed,
+                           "smb2_create_send failed\n");
+
+       state->lock_req->async.fn = test_open_brlock_deadlock_loop_locked;
+       state->lock_req->async.private_data = state;
+       return;
+failed:
+       state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_unlock(
+       struct open_brlock_deadlock_state *state);
+
+static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req)
+{
+       struct open_brlock_deadlock_state *state = req->async.private_data;
+       TALLOC_CTX *frame = talloc_stackframe();
+       NTSTATUS status;
+
+       status = smb2_lock_recv(req, &state->lock);
+       torture_assert_ntstatus_ok_goto(state->tctx, status,
+                                       state->ok, failed, __location__);
+       state->lock_req = NULL;
+
+       TALLOC_FREE(frame);
+       test_open_brlock_deadlock_loop_unlock(state);
+       return;
+
+failed:
+       state->stop = true;
+       TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_unlock(
+       struct open_brlock_deadlock_state *state)
+{
+       if (state->stop) {
+               return;
+       }
+
+       state->el.flags = SMB2_LOCK_FLAG_UNLOCK;
+
+       state->lock_req = smb2_lock_send(state->tree2, &state->lock);
+       torture_assert_goto(state->tctx, state->lock_req != NULL,
+                           state->ok, failed,
+                           "smb2_create_send failed\n");
+
+       state->lock_req->async.fn = test_open_brlock_deadlock_loop_unlocked;
+       state->lock_req->async.private_data = state;
+       return;
+failed:
+       state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req)
+{
+       struct open_brlock_deadlock_state *state = req->async.private_data;
+       TALLOC_CTX *frame = talloc_stackframe();
+       NTSTATUS status;
+
+       status = smb2_lock_recv(req, &state->lock);
+       torture_assert_ntstatus_ok_goto(state->tctx, status,
+                                       state->ok, failed, __location__);
+       state->lock_req = NULL;
+
+       TALLOC_FREE(frame);
+       test_open_brlock_deadlock_loop_lock(state);
+       return;
+
+failed:
+       state->stop = true;
+       TALLOC_FREE(frame);
+}
+
+
+static void test_open_brlock_deadlock_timeout(struct tevent_context *ev,
+                                             struct tevent_timer *te,
+                                             struct timeval current_time,
+                                             void *private_data)
+{
+       struct open_brlock_deadlock_state *state = private_data;
+       state->stop = true;
+}
+
+static bool test_open_brlock_deadlock(struct torture_context *tctx,
+                                     struct smb2_tree *tree1,
+                                     struct smb2_tree *tree2)
+{
+       int timeout_sec = torture_setting_int(tctx, "open_brlock_deadlock_timemout", 0);
+       const char *fname1 = BASEDIR "\\test_open_brlock_deadlock1.txt";
+       const char *fname2 = BASEDIR "\\test_open_brlock_deadlock2.txt";
+       struct open_brlock_deadlock_state state;
+       struct smb2_handle h = {};
+       uint8_t buf[200];
+       struct tevent_timer *te = NULL;
+       NTSTATUS status;
+       bool ret = true;
+
+       if (timeout_sec == 0) {
+               torture_skip_goto(tctx, done, "Test skipped, pass '--option=torture:open_brlock_deadlock_timemout=SEC' to run\n");
+       }
+
+       state = (struct open_brlock_deadlock_state) {
+               .tctx = tctx,
+               .tree1 = tree1,
+               .tree2 = tree2,
+       };
+
+       ret = smb2_util_setup_dir(tctx, tree1, BASEDIR);
+       torture_assert_goto(tctx, ret, ret, done,
+                                  "smb2_util_setup_dir failed");
+
+       status = torture_smb2_testfile(tree1, fname1, &h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "torture_smb2_testfile failed");
+       status = smb2_util_close(tree1, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_util_close failed");
+
+       status = torture_smb2_testfile(tree2, fname2, &h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "torture_smb2_testfile failed");
+
+       status = smb2_util_write(tree2, h, buf, 0, ARRAY_SIZE(buf));
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_util_write failed");
+
+       state.cr = (struct smb2_create) {
+               .in.desired_access = SEC_FILE_READ_DATA,
+               .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+               .in.create_disposition = NTCREATEX_DISP_OPEN,
+               .in.fname = fname1,
+       };
+
+       state.el = (struct smb2_lock_element) {
+               .length = 1,
+               .offset = 0,
+       };
+       state.lock = (struct smb2_lock) {
+               .in.locks = &state.el,
+               .in.lock_count  = 1,
+               .in.file.handle = h,
+       };
+
+       te = tevent_add_timer(tctx->ev,
+                             tctx,
+                             timeval_current_ofs(timeout_sec, 0),
+                             test_open_brlock_deadlock_timeout,
+                             &state);
+       torture_assert_goto(tctx, te != NULL, ret, done, __location__);
+
+       test_open_brlock_deadlock_loop_open(&state);
+       test_open_brlock_deadlock_loop_lock(&state);
+
+       while (!state.stop) {
+               int rc = tevent_loop_once(tctx->ev);
+               torture_assert_int_equal(tctx, rc, 0, "tevent_loop_once");
+       }
+
+done:
+       if (!smb2_util_handle_empty(h)) {
+               smb2_util_close(tree2, h);
+       }
+       smb2_deltree(tree1, BASEDIR);
+       return ret;
+}
+
 /* basic testing of SMB2 locking
 */
 struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx)
@@ -3506,6 +3788,7 @@ struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx)
        torture_suite_add_1smb2_test(suite, "replay_smb3_specification_multi",
                                     test_replay_smb3_specification_multi);
        torture_suite_add_1smb2_test(suite, "ctdb-delrec-deadlock", test_deadlock);
+       torture_suite_add_2smb2_test(suite, "open-brlock-deadlock", test_open_brlock_deadlock);
 
        suite->description = talloc_strdup(suite, "SMB2-LOCK tests");