]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s4:torture/smb2: add smb2.create.mkdir-visible
authorStefan Metzmacher <metze@samba.org>
Fri, 9 Aug 2024 09:06:00 +0000 (11:06 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 21 Aug 2024 08:02:30 +0000 (08:02 +0000)
This reproduces a race where one client creates
a directory and other clients see it before
the directory is fully setup including the correct
permissions and similar things.

We have a DENY ACE for SEC_DIR_ADD_FILE, which means
that files can't be created. This is set on
a base directory 'mkdir_visible'.

Then we have a lot of async loops trying to create
a file called 'mkdir_visible\dir\file_NR'. These loop
as fast as possible expecting OBJECT_PATH_NOT_FOUND,
because 'mkdir_visible\dir' is not there.

Then we send a create for 'mkdir_visible\dir' and
expect that to work.

This should turn the 'mkdir_visible\dir\file_NR' loop
into getting ACCESS_DENIED, because the
DENY ACE for SEC_DIR_ADD_FILE should be inherited
before 'mkdir_visible\dir' is visible to other clients.

Because of the complex steps in mkdir_internal(),
smbd allows the creation 'mkdir_visible\dir\file_NR',
as 'mkdir_visible\dir' is already visible after the
mkdirat(), before the DENY ACE is inherited.

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

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

diff --git a/selftest/knownfail.d/samba3.smb2.create.mkdir-visible b/selftest/knownfail.d/samba3.smb2.create.mkdir-visible
new file mode 100644 (file)
index 0000000..9d16b7c
--- /dev/null
@@ -0,0 +1 @@
+^samba3.smb2.create.mkdir-visible
index c1d132d359e01bfe2c46c9b5487d7940697b0bf6..18e76565f80f64cf9a5e50da4c3e54bac9ede3ee 100644 (file)
@@ -1677,6 +1677,399 @@ done:
        return ret;
 }
 
+/*
+  test SMB2 mkdir with OPEN_IF on the same name twice.
+  Must use 2 connections to hit the race.
+*/
+
+struct test_mkdir_visible_open;
+
+struct test_mkdir_visible_state {
+       struct torture_context *tctx;
+       size_t loops_running;
+       bool ok;
+};
+
+struct test_mkdir_visible_open {
+       struct test_mkdir_visible_state *state;
+       struct smb2_tree *tree;
+       uint64_t try;
+       struct smbXcli_conn *conn;
+       uint32_t timeout_msec;
+       struct smbXcli_session *session;
+       struct smbXcli_tcon *tcon;
+       const char *filename;
+       uint8_t oplock_level;
+       uint32_t impersonation_level;
+       uint32_t desired_access;
+       uint32_t file_attributes;
+       uint32_t share_access;
+       uint32_t create_disposition;
+       uint32_t create_options;
+       struct tevent_req *subreq;
+       NTSTATUS status;
+       uint64_t fid_persistent;
+       uint64_t fid_volatile;
+       struct smb_create_returns cr;
+       bool done;
+};
+
+static void test_mkdir_visible_open_retry(struct tevent_req *subreq)
+{
+       struct test_mkdir_visible_open *op =
+               tevent_req_callback_data(subreq,
+               struct test_mkdir_visible_open);
+       struct test_mkdir_visible_state *state = op->state;
+       struct torture_context *tctx = state->tctx;
+
+       if (!state->ok) {
+               return;
+       }
+
+       torture_assert_goto(tctx, op->subreq == subreq,
+                           state->ok, done, "subreq");
+       op->subreq = NULL;
+
+       op->status = smb2cli_create_recv(subreq,
+                                        &op->fid_persistent,
+                                        &op->fid_volatile,
+                                        &op->cr,
+                                        op,
+                                        NULL,
+                                        NULL);
+       TALLOC_FREE(subreq);
+       torture_comment(tctx, "%s:%s: try[%"PRIu64"] %s\n",
+                       __func__, op->filename, op->try,
+                       nt_errstr(op->status));
+       if (NT_STATUS_EQUAL(op->status, NT_STATUS_ACCESS_DENIED)) {
+               goto done;
+       }
+       torture_assert_ntstatus_equal_goto(tctx,
+               op->status, NT_STATUS_OBJECT_PATH_NOT_FOUND,
+               state->ok, done, "smb2cli_create_recv");
+
+       op->try += 1;
+       torture_comment(tctx, "%s:%s: try[%"PRIu64"] starting\n",
+                       __func__, op->filename, op->try);
+       subreq = smb2cli_create_send(op,
+                                    tctx->ev,
+                                    op->conn,
+                                    op->timeout_msec,
+                                    op->session,
+                                    op->tcon,
+                                    op->filename,
+                                    op->oplock_level,
+                                    op->impersonation_level,
+                                    op->desired_access,
+                                    op->file_attributes,
+                                    op->share_access,
+                                    op->create_disposition,
+                                    op->create_options,
+                                    NULL);
+       torture_assert_not_null_goto(tctx, subreq,
+                                    state->ok, done,
+                                    "smb2cli_create_send");
+       tevent_req_set_callback(subreq,
+                               test_mkdir_visible_open_retry,
+                               op);
+       op->subreq = subreq;
+       return;
+
+done:
+       op->done = true;
+       state->loops_running -= 1;
+}
+
+static void test_mkdir_visible_open_done(struct tevent_req *subreq)
+{
+       struct test_mkdir_visible_open *op =
+               tevent_req_callback_data(subreq,
+               struct test_mkdir_visible_open);
+       struct test_mkdir_visible_state *state = op->state;
+       struct torture_context *tctx = state->tctx;
+
+       if (!state->ok) {
+               return;
+       }
+
+       torture_assert_goto(tctx, op->subreq == subreq,
+                           state->ok, done, "subreq");
+       op->subreq = NULL;
+
+       op->status = smb2cli_create_recv(subreq,
+                                        &op->fid_persistent,
+                                        &op->fid_volatile,
+                                        &op->cr,
+                                        op,
+                                        NULL,
+                                        NULL);
+       TALLOC_FREE(subreq);
+       torture_comment(tctx, "%s:%s: %s\n",
+                       __func__, op->filename,
+                       nt_errstr(op->status));
+       torture_assert_ntstatus_ok_goto(tctx, op->status,
+               state->ok, done, "smb2cli_create_recv");
+
+done:
+       op->done = true;
+}
+
+static bool test_mkdir_visible(struct torture_context *tctx,
+                              struct smb2_tree *tree)
+{
+       struct test_mkdir_visible_state *state = NULL;
+       struct test_mkdir_visible_open templ = {
+               .state = NULL,
+       };
+       struct test_mkdir_visible_open *dop = NULL;
+       size_t num_loops = 50;
+       struct test_mkdir_visible_open **loops = NULL;
+       const char *base_dname = "mkdir_visible";
+       const char *file_ok = NULL;
+       const char *file_fail = NULL;
+       struct smb2_handle bdh = {{}};
+       struct smb2_handle h = {{}};
+       union smb_fileinfo q = {};
+       union smb_setfileinfo setinfo = {};
+       struct security_descriptor *sd = NULL;
+       struct security_ace *ace = NULL;
+       NTSTATUS status;
+       bool ret = true;
+       size_t i;
+
+       smb2_keepalive(tree->session->transport);
+       smb2_deltree(tree, base_dname);
+       smb2_keepalive(tree->session->transport);
+
+       torture_comment(tctx,
+               "Testing SMB2 Create Directory is visible with multiple connections\n");
+
+       state = talloc_zero(tctx, struct test_mkdir_visible_state);
+       torture_assert_not_null_goto(tctx, state, ret, done, "talloc_zero");
+       state->tctx = tctx;
+       state->ok = true;
+
+       /*
+        * We create a base directory that has an inheritiable
+        * ACE to deny SEC_DIR_ADD_FILE.
+        */
+
+       status = torture_smb2_testdir(tree, base_dname, &bdh);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "torture_smb2_testdir");
+
+       file_ok = talloc_asprintf(state, "%s\\file_ok", base_dname);
+       torture_assert_not_null_goto(tctx, file_ok, ret, done,
+                                    "talloc_asprintf");
+       file_fail = talloc_asprintf(state, "%s\\file_fail", base_dname);
+       torture_assert_not_null_goto(tctx, file_fail, ret, done,
+                                    "talloc_asprintf");
+
+       status = torture_smb2_testfile(tree, file_ok, &h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "torture_smb2_testfile(file_ok)");
+       status = smb2_util_close(tree, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_util_close(file_ok)");
+
+       q.query_secdesc.level = RAW_FILEINFO_SEC_DESC;
+       q.query_secdesc.in.file.handle = bdh;
+       q.query_secdesc.in.secinfo_flags = SECINFO_DACL;
+       status = smb2_getinfo_file(tree, state, &q);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "RAW_FILEINFO_SEC_DESC");
+       sd = q.query_secdesc.out.sd;
+       torture_assert_not_null_goto(tctx, sd, ret, done,
+                                    "q.query_secdesc.out.sd");
+       torture_assert_not_null_goto(tctx, sd->dacl, ret, done,
+                                    "q.query_secdesc.out.sd->dacl");
+
+       sd = security_descriptor_copy(state, sd);
+       torture_assert_not_null_goto(tctx, sd, ret, done,
+                                    "security_descriptor_copy");
+       ace = security_ace_create(sd,
+                                 SID_WORLD,
+                                 SEC_ACE_TYPE_ACCESS_DENIED,
+                                 SEC_DIR_ADD_FILE,
+                                 SEC_ACE_FLAG_CONTAINER_INHERIT);
+       torture_assert_not_null_goto(tctx, ace, ret, done,
+                                    "security_ace_create");
+       status = security_descriptor_dacl_insert(sd, ace, 0);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "security_descriptor_dacl_insert");
+
+       setinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC;
+       setinfo.set_secdesc.in.file.handle = bdh;
+       setinfo.set_secdesc.in.secinfo_flags = SECINFO_DACL;
+       setinfo.set_secdesc.in.sd = sd;
+       status = smb2_setinfo_file(tree, &setinfo);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "RAW_SFILEINFO_SEC_DESC");
+
+       /*
+        * Make sure the new deny ACE works
+        */
+       status = torture_smb2_testfile(tree, file_fail, &h);
+       torture_assert_ntstatus_equal_goto(tctx, status,
+                                          NT_STATUS_ACCESS_DENIED,
+                                          ret, done,
+                                          "torture_smb2_testfile(file_ok)");
+
+       status = smb2_util_close(tree, bdh);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "smb2_util_close(base_dname)");
+
+       templ.state = state;
+       templ.timeout_msec = 30000;
+       templ.oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+       templ.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+       templ.desired_access = SEC_FILE_READ_ATTRIBUTE;
+       templ.file_attributes = FILE_ATTRIBUTE_NORMAL;
+       templ.share_access = FILE_SHARE_READ |
+                            FILE_SHARE_WRITE |
+                            FILE_SHARE_DELETE;
+       templ.create_disposition = FILE_CREATE;
+       templ.create_options = FILE_NON_DIRECTORY_FILE;
+       templ.status = NT_STATUS_REQUEST_OUT_OF_SEQUENCE;
+
+       dop = talloc_zero(state, struct test_mkdir_visible_open);
+       torture_assert_not_null_goto(tctx, dop, ret, done, "talloc_zero");
+       *dop = templ;
+       dop->filename = talloc_asprintf(dop, "%s\\visible_dir", base_dname);
+       torture_assert_not_null_goto(tctx, dop->filename, ret, done,
+                                    "talloc_asprintf");
+       dop->desired_access |= SEC_STD_READ_CONTROL;
+       dop->file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+       dop->create_options = FILE_DIRECTORY_FILE;
+       dop->tree = tree;
+       dop->conn = tree->session->transport->conn;
+       dop->session = tree->session->smbXcli;
+       dop->tcon = tree->smbXcli;
+
+       loops = talloc_zero_array(state,
+                                 struct test_mkdir_visible_open *,
+                                 num_loops);
+       torture_assert_not_null_goto(tctx, loops, ret, done,
+                                    "talloc_zero_array");
+
+       for (i = 0; i < num_loops; i++) {
+               struct test_mkdir_visible_open *op = NULL;
+
+               op = talloc_zero(loops, struct test_mkdir_visible_open);
+               torture_assert_not_null_goto(tctx, op, ret, done, "talloc_zero");
+
+               *op = templ;
+               op->filename = talloc_asprintf(op, "%s\\visible_dir\\file_%zu",
+                                              base_dname, i);
+               torture_assert_not_null_goto(tctx, op->filename, ret, done,
+                                            "talloc_asprintf");
+
+               ret = torture_smb2_connection(tctx, &op->tree);
+               torture_assert_goto(tctx, ret, ret, done,
+                                   "torture_smb2_connection");
+
+               op->conn = op->tree->session->transport->conn;
+               op->session = op->tree->session->smbXcli;
+               op->tcon = op->tree->smbXcli;
+
+               loops[i] = op;
+       }
+
+       for (i = 0; i < num_loops; i++) {
+               struct test_mkdir_visible_open *op = loops[i];
+
+               op->try = 1;
+               torture_comment(tctx, "%s:%s: try[%"PRIu64"] starting\n",
+                               __func__, op->filename, op->try);
+               op->subreq = smb2cli_create_send(op,
+                                                tctx->ev,
+                                                op->conn,
+                                                op->timeout_msec,
+                                                op->session,
+                                                op->tcon,
+                                                op->filename,
+                                                op->oplock_level,
+                                                op->impersonation_level,
+                                                op->desired_access,
+                                                op->file_attributes,
+                                                op->share_access,
+                                                op->create_disposition,
+                                                op->create_options,
+                                                NULL);
+               torture_assert_not_null_goto(tctx, op->subreq,
+                                            state->ok, done,
+                                            "smb2cli_create_send");
+               tevent_req_set_callback(op->subreq,
+                                       test_mkdir_visible_open_retry,
+                                       op);
+               state->loops_running += 1;
+       }
+
+       torture_comment(tctx, "%s:%s: starting\n",
+                       __func__, dop->filename);
+       dop->subreq = smb2cli_create_send(dop,
+                                         tctx->ev,
+                                         dop->conn,
+                                         dop->timeout_msec,
+                                         dop->session,
+                                         dop->tcon,
+                                         dop->filename,
+                                         dop->oplock_level,
+                                         dop->impersonation_level,
+                                         dop->desired_access,
+                                         dop->file_attributes,
+                                         dop->share_access,
+                                         dop->create_disposition,
+                                         dop->create_options,
+                                         NULL);
+       torture_assert_not_null_goto(tctx, dop->subreq,
+                                    state->ok, done,
+                                    "smb2cli_create_send");
+       tevent_req_set_callback(dop->subreq,
+                               test_mkdir_visible_open_done,
+                               dop);
+
+       ret = tevent_req_poll(dop->subreq, tctx->ev);
+       torture_assert_goto(tctx, ret, ret, done, "tevent_req_poll(dop)");
+       torture_assert_goto(tctx, dop->done, ret, done, "dop->done after dop");
+
+       torture_assert_goto(tctx, state->ok, ret, done, "state->ok after dop");
+
+       while (state->ok && state->loops_running > 0) {
+               int lret = tevent_loop_once(tctx->ev);
+               torture_assert_int_equal_goto(tctx, lret, 0,
+                                             ret, done,
+                                             "tevent_loop_once()");
+       }
+
+       torture_assert_goto(tctx, state->ok, ret, done, "state->ok after loop");
+       torture_assert_int_equal_goto(tctx, state->loops_running, 0,
+                                     ret, done,
+                                     "state->loops_running after loop");
+
+       for (i = 0; i < num_loops; i++) {
+               struct test_mkdir_visible_open *op = loops[i];
+
+               torture_assert_goto(tctx, op->done, ret, done,
+                                   "op->done after loop");
+
+               torture_comment(tctx, "%s:%s: try[%"PRIu64"] checking...\n",
+                               __func__, op->filename, op->try);
+
+               torture_assert_ntstatus_equal_goto(tctx,
+                       op->status, NT_STATUS_ACCESS_DENIED,
+                       ret, done, "smb2cli_create_recv");
+       }
+
+done:
+       TALLOC_FREE(state);
+       smb2_keepalive(tree->session->transport);
+       smb2_deltree(tree, base_dname);
+       smb2_keepalive(tree->session->transport);
+
+       return ret;
+}
+
 /*
   test directory creation with an initial allocation size > 0
 */
@@ -3538,6 +3931,7 @@ struct torture_suite *torture_smb2_create_init(TALLOC_CTX *ctx)
        torture_suite_add_1smb2_test(suite, "acldir", test_create_acl_dir);
        torture_suite_add_1smb2_test(suite, "nulldacl", test_create_null_dacl);
        torture_suite_add_1smb2_test(suite, "mkdir-dup", test_mkdir_dup);
+       torture_suite_add_1smb2_test(suite, "mkdir-visible", test_mkdir_visible);
        torture_suite_add_1smb2_test(suite, "dir-alloc-size", test_dir_alloc_size);
        torture_suite_add_1smb2_test(suite, "dosattr_tmp_dir", test_dosattr_tmp_dir);
        torture_suite_add_1smb2_test(suite, "quota-fake-file", test_smb2_open_quota_fake_file);