mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
s4:torture/smb2: add smb2.create.mkdir-visible
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>
This commit is contained in:
parent
ffc75c569c
commit
f9d7a930f0
1
selftest/knownfail.d/samba3.smb2.create.mkdir-visible
Normal file
1
selftest/knownfail.d/samba3.smb2.create.mkdir-visible
Normal file
@ -0,0 +1 @@
|
||||
^samba3.smb2.create.mkdir-visible
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user