]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
smbtorture: add a test for recursive h-lease break when renaming
authorRalph Boehme <slow@samba.org>
Wed, 2 Oct 2024 05:47:25 +0000 (07:47 +0200)
committerRalph Boehme <slow@samba.org>
Tue, 5 Nov 2024 14:39:30 +0000 (14:39 +0000)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15608

Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
selftest/knownfail.d/samba3.smb2.lease [new file with mode: 0644]
source4/torture/smb2/lease.c
source4/torture/smb2/lease_break_handler.h

diff --git a/selftest/knownfail.d/samba3.smb2.lease b/selftest/knownfail.d/samba3.smb2.lease
new file mode 100644 (file)
index 0000000..499570c
--- /dev/null
@@ -0,0 +1 @@
+^samba3.smb2.lease.rename_dir_openfile\(fileserver\)
index 0bebbef4a4c43017b8fd0b03e280509f0d9b0ac2..76a946c87de7d836562e22300a493c5070940c96 100644 (file)
@@ -5066,6 +5066,342 @@ done:
        return ret;
 }
 
+struct rename_tcase_open {
+       bool hlease;
+       bool close_on_break;
+};
+
+struct rename_tcase {
+       const char *name;
+       bool disabled;
+       struct rename_tcase_open o1;
+       struct rename_tcase_open o2;
+       bool do_o3;
+       struct rename_tcase_open o3;
+       NTSTATUS status;
+};
+
+static bool torture_rename_dir_openfile_do(struct torture_context *tctx,
+                                          struct smb2_tree *tree1,
+                                          struct smb2_tree *tree2,
+                                          struct rename_tcase *t)
+{
+       struct smb2_create c = {};
+       union smb_setfileinfo sinfo = {};
+       struct smb2_handle d1 = {};
+       struct smb2_handle h1 = {};
+       struct smb2_handle h2 = {};
+       struct smb2_handle h3 = {};
+       struct smb2_handle *h = NULL;
+       struct smb2_lease *please1 = NULL;
+       struct smb2_lease *please2 = NULL;
+       struct smb2_lease *please3 = NULL;
+       struct smb2_lease lease1 = {};
+       struct smb2_lease lease2 = {};
+       struct smb2_lease lease3 = {};
+       struct smb2_request *req = NULL;
+       struct smb2_lease_break_ack ack = {};
+       struct rename_tcase_open *to = NULL;
+       const char *dname = "torture_rename_dir_openfile_dir";
+       const char *fname1 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file1";
+       const char *fname2 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file2";
+       const char *fname3 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file3";
+       const char *new_dname = "torture_rename_dir_openfile_dir-renamed";
+       bool expect_immediate_fail = false;
+       bool ret = true;
+       NTSTATUS status;
+
+       torture_comment(tctx, "Subtest: %s\n", t->name);
+       if (t->disabled) {
+               torture_comment(tctx, "...skipped\n");
+               return true;
+       }
+
+       tree2->session->transport->lease.handler = torture_lease_handler;
+       tree2->session->transport->lease.private_data = tree2;
+       torture_reset_lease_break_info(tctx, &lease_break_info);
+       lease_break_info.lease_skip_ack = true;
+
+       smb2_deltree(tree1, dname);
+       smb2_deltree(tree1, new_dname);
+
+       torture_comment(tctx, "Creating base directory\n");
+
+       smb2_lease_v2_create_share(&c, NULL, true, dname,
+                                  smb2_util_share_access("RWD"),
+                                  0,
+                                  NULL,
+                                  smb2_util_lease_state(""),
+                                  0);
+       status = smb2_create(tree1, tree1, &c);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_create failed\n");
+       d1 = c.out.file.handle;
+
+       torture_comment(tctx, "Creating test file1\n");
+
+       if (t->o1.hlease) {
+               please1 = &lease1;
+       }
+       smb2_lease_v2_create_share(&c, please1, false, fname1,
+                                  smb2_util_share_access("RWD"),
+                                  LEASE1,
+                                  NULL,
+                                  smb2_util_lease_state("RH"),
+                                  0);
+       status = smb2_create(tree2, tree2, &c);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_create failed\n");
+       h1 = c.out.file.handle;
+
+       torture_comment(tctx, "Creating test file2\n");
+
+       if (t->o2.hlease) {
+               please2 = &lease2;
+       }
+       smb2_lease_v2_create_share(&c, please2, false, fname2,
+                                  smb2_util_share_access("RWD"),
+                                  LEASE2,
+                                  NULL,
+                                  smb2_util_lease_state("RH"),
+                                  0);
+       status = smb2_create(tree2, tree2, &c);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_create failed\n");
+       h2 = c.out.file.handle;
+
+       torture_comment(tctx, "Renaming directory\n");
+
+       sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
+       sinfo.rename_information.in.file.handle = d1;
+       sinfo.rename_information.in.new_name = new_dname;
+
+       req = smb2_setinfo_file_send(tree1, &sinfo);
+       torture_assert_goto(tctx, req != NULL, ret, done,
+                           "smb2_setinfo_file_send");
+
+       torture_assert(tctx, req->state == SMB2_REQUEST_RECV, "bad req state");
+
+       if (t->o1.hlease || t->o2.hlease) {
+               /* Get the first break */
+               torture_wait_for_lease_break(tctx);
+
+               if (lease_break_info.count == 0) {
+                       /*
+                        * If one of the two opens was without a h-lease, the
+                        * scan for opens might hit the open without h-lease
+                        * first triggering an immediate STATUS_ACCESS_DENIED
+                        * for the rename without sending out any lease break.
+                        */
+                       torture_assert_goto(tctx, (!t->o1.hlease || !t->o2.hlease),
+                                           ret, done,
+                                           "Expected only one hlease when getting no hlease break\n");
+
+                       status = smb2_setinfo_recv(req);
+                       torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done,
+                                                          "Rename didn't work as expected\n");
+                       goto done;
+               }
+
+               if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 &&
+                   lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1)
+               {
+                       torture_comment(tctx, "Got break for file 1\n");
+                       please1 = &lease1;
+                       h = &h1;
+                       to = &t->o1;
+               } else {
+                       torture_comment(tctx, "Got break for file 2\n");
+                       please1 = &lease2;
+                       h = &h2;
+                       to = &t->o2;
+               }
+               please1->lease_epoch += 2;
+
+               CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport,
+                                          "RH", "R",
+                                          please1->lease_key.data[0],
+                                          please1->lease_epoch);
+
+               ack.in.lease.lease_key = lease_break_info.lease_break.current_lease.lease_key;
+               ack.in.lease.lease_state = lease_break_info.lease_break.new_lease_state;
+               torture_reset_lease_break_info(tctx, &lease_break_info);
+               lease_break_info.lease_skip_ack = true;
+
+               if (to->close_on_break) {
+                       status = smb2_util_close(tree2, *h);
+                       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                                       "smb2_util_close failed\n");
+                       ZERO_STRUCTP(h);
+               } else {
+                       status = smb2_lease_break_ack(tree2, &ack);
+                       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                                       "ack failed\n");
+                       expect_immediate_fail = true;
+               }
+       }
+
+       if (t->do_o3) {
+               torture_comment(tctx, "Doing additional open after first break\n");
+
+               if (t->o3.hlease) {
+                       please3 = &lease3;
+               }
+               smb2_lease_v2_create_share(&c, please3, false, fname3,
+                                          smb2_util_share_access("RWD"),
+                                          LEASE3,
+                                          NULL,
+                                          smb2_util_lease_state("RH"),
+                                          0);
+               status = smb2_create(tree2, tree2, &c);
+               torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                               "smb2_create failed\n");
+               h3 = c.out.file.handle;
+       }
+
+       if (!expect_immediate_fail && t->o1.hlease && t->o2.hlease) {
+               /* Get the second break */
+               torture_wait_for_lease_break(tctx);
+
+               if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 &&
+                   lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1)
+               {
+                       torture_comment(tctx, "Got break for file 1\n");
+                       please1 = &lease1;
+                       h = &h1;
+                       to = &t->o1;
+               } else {
+                       torture_comment(tctx, "Got break for file 2\n");
+                       please1 = &lease2;
+                       h = &h2;
+                       to = &t->o2;
+               }
+               please1->lease_epoch += 2;
+
+               CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport,
+                                          "RH", "R",
+                                          please1->lease_key.data[0],
+                                          please1->lease_epoch);
+
+               ack.in.lease.lease_key = lease_break_info.lease_break.current_lease.lease_key;
+               ack.in.lease.lease_state = lease_break_info.lease_break.new_lease_state;
+               torture_reset_lease_break_info(tctx, &lease_break_info);
+               lease_break_info.lease_skip_ack = true;
+
+               if (to->close_on_break) {
+                       status = smb2_util_close(tree2, *h);
+                       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                                       "smb2_util_close failed\n");
+                       ZERO_STRUCTP(h);
+               } else {
+                       status = smb2_lease_break_ack(tree2, &ack);
+                       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                                       "ack failed\n");
+               }
+       }
+
+       status = smb2_setinfo_recv(req);
+       torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done,
+                                          "Rename didn't work as expected\n");
+
+done:
+       if (!smb2_util_handle_empty(d1)) {
+               smb2_util_close(tree1, d1);
+       }
+       if (!smb2_util_handle_empty(h1)) {
+               smb2_util_close(tree2, h1);
+       }
+       if (!smb2_util_handle_empty(h2)) {
+               smb2_util_close(tree2, h2);
+       }
+       if (!smb2_util_handle_empty(h3)) {
+               smb2_util_close(tree2, h3);
+       }
+       smb2_deltree(tree1, dname);
+       smb2_deltree(tree1, new_dname);
+       return ret;
+}
+
+static bool torture_rename_dir_openfile(struct torture_context *tctx,
+                                       struct smb2_tree *tree1,
+                                       struct smb2_tree *tree2)
+{
+       struct rename_tcase tcases[] = {
+               {
+                       .name = "two-hleases-two-closes",
+                       .o1 = { .hlease = true, .close_on_break = true },
+                       .o2 = { .hlease = true, .close_on_break = true },
+                       .do_o3 = false,
+                       .status = NT_STATUS_OK,
+               },
+               {
+                       .name = "no-hleases",
+                       .o1 = { .hlease = false, },
+                       .o2 = { .hlease = false, },
+                       .do_o3 = false,
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "two-hleases-second-hlease-close",
+                       .o1 = { .hlease = true, .close_on_break = false },
+                       .o2 = { .hlease = true, .close_on_break = true },
+                       .do_o3 = false,
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "two-hleases-first-hlease-close",
+                       .o1 = { .hlease = true, .close_on_break = true },
+                       .o2 = { .hlease = true, .close_on_break = false },
+                       .do_o3 = false,
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "first-hlease-close",
+                       .o1 = { .hlease = true, .close_on_break = true },
+                       .o2 = { .hlease = false, },
+                       .do_o3 = false,
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "second-hlease-close",
+                       .o1 = { .hlease = false, },
+                       .o2 = { .hlease = true, .close_on_break = true },
+                       .do_o3 = false,
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "two-hleases-two-closes-addopen-w-hlease",
+                       .o1 = { .hlease = true, .close_on_break = true },
+                       .o2 = { .hlease = true, .close_on_break = true },
+                       .do_o3 = true,
+                       .o3 = { .hlease = true, .close_on_break = true },
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+               {
+                       .name = "two-hleases-two-closes-addopen-wo-hlease",
+                       .o1 = { .hlease = true, .close_on_break = true },
+                       .o2 = { .hlease = true, .close_on_break = true },
+                       .do_o3 = true,
+                       .o3 = { .hlease = false, },
+                       .status = NT_STATUS_ACCESS_DENIED,
+               },
+       };
+       size_t i;
+       bool ret;
+
+       tree1->session->transport->lease.handler = torture_lease_handler;
+       tree1->session->transport->lease.private_data = tree2;
+       torture_reset_lease_break_info(tctx, &lease_break_info);
+
+       for (i = 0; i < ARRAY_SIZE(tcases); i++) {
+               ret = torture_rename_dir_openfile_do(tctx, tree1, tree2, &tcases[i]);
+               torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+       }
+
+done:
+       return ret;
+}
+
 struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx)
 {
        struct torture_suite *suite =
@@ -5126,6 +5462,8 @@ struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx)
                                     test_initial_delete_logoff);
        torture_suite_add_1smb2_test(suite, "initial_delete_disconnect",
                                     test_initial_delete_disconnect);
+       torture_suite_add_2smb2_test(suite, "rename_dir_openfile",
+                                    torture_rename_dir_openfile);
 
        suite->description = talloc_strdup(suite, "SMB2-LEASE tests");
 
index 90fde1a92174d4c48565ea8b73c117d6b7266ad1..f839bcaeaa370fc040e14a23f53ea31b155484a6 100644 (file)
@@ -100,6 +100,22 @@ struct lease_break_info {
                }                                                       \
        } while(0)
 
+#define _CHECK_BREAK_INFO_NOWAIT(__oldstate, __state, __key)                   \
+       do {                                                            \
+               CHECK_VAL(lease_break_info.failures, 0);                        \
+               CHECK_VAL(lease_break_info.count, 1);                           \
+               CHECK_LEASE_BREAK(&lease_break_info.lease_break, (__oldstate), \
+                   (__state), (__key));                                \
+               if (!lease_break_info.lease_skip_ack && \
+                   (lease_break_info.lease_break.break_flags &         \
+                    SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED))        \
+               {       \
+                       torture_wait_for_lease_break(tctx);             \
+                       CHECK_LEASE_BREAK_ACK(&lease_break_info.lease_break_ack, \
+                                             (__state), (__key));      \
+               }                                                       \
+       } while(0)
+
 #define CHECK_BREAK_INFO(__oldstate, __state, __key)                   \
        do {                                                            \
                _CHECK_BREAK_INFO(__oldstate, __state, __key);          \
@@ -116,6 +132,16 @@ struct lease_break_info {
                } \
        } while(0)
 
+#define CHECK_BREAK_INFO_V2_NOWAIT(__transport, __oldstate, __state, __key, __epoch) \
+       do {                                                            \
+               _CHECK_BREAK_INFO_NOWAIT(__oldstate, __state, __key);           \
+               CHECK_VAL(lease_break_info.lease_break.new_epoch, __epoch);     \
+               if (!TARGET_IS_SAMBA3(tctx)) {                          \
+                       CHECK_VAL((uintptr_t)lease_break_info.lease_transport, \
+                                 (uintptr_t)__transport);              \
+               } \
+       } while(0)
+
 extern struct lease_break_info lease_break_info;
 
 bool torture_lease_handler(struct smb2_transport *transport,