/* Unix SMB/CIFS implementation. SMB2 create test suite Copyright (C) Andrew Tridgell 2008 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "libcli/smb/smbXcli_base.h" #include "torture/torture.h" #include "torture/util.h" #include "torture/smb2/proto.h" #include "librpc/gen_ndr/ndr_security.h" #include "libcli/security/security.h" #include "system/filesys.h" #include "auth/credentials/credentials.h" #include "lib/cmdline/cmdline.h" #include "librpc/gen_ndr/security.h" #include "lib/events/events.h" #define FNAME "test_create.dat" #define DNAME "smb2_open" #define CHECK_STATUS(status, correct) do { \ if (!NT_STATUS_EQUAL(status, correct)) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) Incorrect status %s - should be %s\n", \ __location__, nt_errstr(status), nt_errstr(correct)); \ return false; \ }} while (0) #define CHECK_EQUAL(v, correct) do { \ if (v != correct) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) Incorrect value for %s 0x%08llx - " \ "should be 0x%08llx\n", \ __location__, #v, \ (unsigned long long)v, \ (unsigned long long)correct); \ return false; \ }} while (0) #define CHECK_TIME(t, field) do { \ time_t t1, t2; \ finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ finfo.all_info.in.file.handle = h1; \ status = smb2_getinfo_file(tree, tctx, &finfo); \ CHECK_STATUS(status, NT_STATUS_OK); \ t1 = t & ~1; \ t2 = nt_time_to_unix(finfo.all_info.out.field) & ~1; \ if (abs(t1-t2) > 2) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) wrong time for field %s %s - %s\n", \ __location__, #field, \ timestring(tctx, t1), \ timestring(tctx, t2)); \ dump_all_info(tctx, &finfo); \ ret = false; \ }} while (0) #define CHECK_NTTIME(t, field) do { \ NTTIME t2; \ finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ finfo.all_info.in.file.handle = h1; \ status = smb2_getinfo_file(tree, tctx, &finfo); \ CHECK_STATUS(status, NT_STATUS_OK); \ t2 = finfo.all_info.out.field; \ if (llabs((int64_t)(t-t2)) > 20000) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) wrong time for field %s %s - %s\n", \ __location__, #field, \ nt_time_string(tctx, t), \ nt_time_string(tctx, t2)); \ dump_all_info(tctx, &finfo); \ ret = false; \ }} while (0) #define CHECK_ALL_INFO(v, field) do { \ finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ finfo.all_info.in.file.handle = h1; \ status = smb2_getinfo_file(tree, tctx, &finfo); \ CHECK_STATUS(status, NT_STATUS_OK); \ if ((v) != (finfo.all_info.out.field)) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) wrong value for field %s 0x%x - 0x%x\n", \ __location__, #field, (int)v,\ (int)(finfo.all_info.out.field)); \ dump_all_info(tctx, &finfo); \ ret = false; \ }} while (0) #define CHECK_VAL(v, correct) do { \ if ((v) != (correct)) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) wrong value for %s 0x%x - should be 0x%x\n", \ __location__, #v, (int)(v), (int)correct); \ ret = false; \ }} while (0) #define SET_ATTRIB(sattrib) do { \ union smb_setfileinfo sfinfo; \ ZERO_STRUCT(sfinfo.basic_info.in); \ sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; \ sfinfo.basic_info.in.file.handle = h1; \ sfinfo.basic_info.in.attrib = sattrib; \ status = smb2_setinfo_file(tree, &sfinfo); \ if (!NT_STATUS_IS_OK(status)) { \ torture_comment(tctx, \ "(%s) Failed to set attrib 0x%x on %s\n", \ __location__, (unsigned int)(sattrib), fname); \ }} while (0) /* test some interesting combinations found by gentest */ static bool test_create_gentest(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create io; NTSTATUS status; uint32_t access_mask, file_attributes_set; uint32_t ok_mask, not_supported_mask, invalid_parameter_mask; uint32_t not_a_directory_mask, unexpected_mask; union smb_fileinfo q; ZERO_STRUCT(io); io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE| NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE; io.in.create_options = 0; io.in.fname = FNAME; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_close(tree, io.out.file.handle); CHECK_STATUS(status, NT_STATUS_OK); io.in.create_options = 0xF0000000; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); io.in.create_options = 0; io.in.file_attributes = FILE_ATTRIBUTE_DEVICE; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); io.in.file_attributes = FILE_ATTRIBUTE_VOLUME; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); io.in.create_disposition = NTCREATEX_DISP_OPEN; io.in.file_attributes = FILE_ATTRIBUTE_VOLUME; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); io.in.create_disposition = NTCREATEX_DISP_CREATE; io.in.desired_access = 0x08000000; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); io.in.desired_access = 0x04000000; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); io.in.file_attributes = 0; io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; ok_mask = 0; not_supported_mask = 0; invalid_parameter_mask = 0; not_a_directory_mask = 0; unexpected_mask = 0; { int i; for (i=0;i<32;i++) { io.in.create_options = (uint32_t)1<ev == NULL) || (trees == NULL) || (requests == NULL) || (ios == NULL)) { torture_comment(tctx, ("talloc failed\n")); ret = false; goto done; } tree->session->transport->options.request_timeout = 60; for (i=0; isession->transport->options.request_timeout = 60; } /* cleanup */ smb2_util_unlink(tree, fname); /* base ntcreatex parms */ ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; io.smb2.in.alloc_size = 0; io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE| NTCREATEX_SHARE_ACCESS_DELETE; io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; io.smb2.in.create_options = 0; io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; io.smb2.in.security_flags = 0; io.smb2.in.fname = fname; io.smb2.in.create_flags = 0; for (i=0; istate < SMB2_REQUEST_DONE) { unreplied = true; break; } status = smb2_create_recv(requests[i], tctx, &(ios[i].smb2)); torture_comment(tctx, "File %d returned status %s\n", i, nt_errstr(status)); if (NT_STATUS_IS_OK(status)) { num_ok += 1; } if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { num_collision += 1; } requests[i] = NULL; } if (!unreplied) { break; } if (tevent_loop_once(tctx->ev) != 0) { torture_comment(tctx, "tevent_loop_once failed\n"); ret = false; goto done; } } if ((num_ok != 1) || (num_ok + num_collision != num_files)) { ret = false; } done: smb2_deltree(tree, fname); return ret; } /* test opening for delete on a read-only attribute file. */ static bool test_smb2_open_for_delete(struct torture_context *tctx, struct smb2_tree *tree) { union smb_open io; union smb_fileinfo finfo; const char *fname = DNAME "\\torture_open_for_delete.txt"; NTSTATUS status; struct smb2_handle h, h1; bool ret = true; torture_comment(tctx, "Checking SMB2_OPEN for delete on a readonly file.\n"); smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); status = torture_smb2_testdir(tree, DNAME, &h); CHECK_STATUS(status, NT_STATUS_OK); /* reasonable default parameters */ ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; io.smb2.in.alloc_size = 0; io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; io.smb2.in.file_attributes = FILE_ATTRIBUTE_READONLY; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; io.smb2.in.create_options = 0; io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; io.smb2.in.security_flags = 0; io.smb2.in.fname = fname; /* Create the readonly file. */ status = smb2_create(tree, tctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); h1 = io.smb2.out.file.handle; CHECK_VAL(io.smb2.out.oplock_level, 0); io.smb2.in.create_options = 0; CHECK_VAL(io.smb2.out.create_action, NTCREATEX_ACTION_CREATED); CHECK_ALL_INFO(io.smb2.out.file_attr, attrib); smb2_util_close(tree, h1); /* Now try and open for delete only - should succeed. */ io.smb2.in.desired_access = SEC_STD_DELETE; io.smb2.in.file_attributes = 0; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; status = smb2_create(tree, tctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, io.smb2.out.file.handle); /* Clear readonly flag to allow file deletion */ io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_ATTRIBUTE; status = smb2_create(tree, tctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); h1 = io.smb2.out.file.handle; SET_ATTRIB(FILE_ATTRIBUTE_ARCHIVE); smb2_util_close(tree, h1); smb2_util_close(tree, h); smb2_util_unlink(tree, fname); smb2_deltree(tree, DNAME); return ret; } /* test SMB2 open with a leading slash on the path. Trying to create a directory with a leading slash should give NT_STATUS_INVALID_PARAMETER error */ static bool test_smb2_leading_slash(struct torture_context *tctx, struct smb2_tree *tree) { union smb_open io; const char *dnameslash = "\\"DNAME; NTSTATUS status; bool ret = true; torture_comment(tctx, "Trying to create a directory with leading slash on path\n"); smb2_deltree(tree, dnameslash); ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.oplock_level = 0; io.smb2.in.desired_access = SEC_RIGHTS_DIR_ALL; io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.smb2.in.fname = dnameslash; status = smb2_create(tree, tree, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); smb2_deltree(tree, dnameslash); return ret; } /* test SMB2 open with an invalid impersonation level. Should give NT_STATUS_BAD_IMPERSONATION_LEVEL error */ static bool test_smb2_impersonation_level(struct torture_context *tctx, struct smb2_tree *tree) { union smb_open io; const char *fname = DNAME "\\torture_invalid_impersonation_level.txt"; NTSTATUS status; struct smb2_handle h; bool ret = true; torture_comment(tctx, "Testing SMB2 open with an invalid impersonation level.\n"); smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, DNAME); status = torture_smb2_testdir(tree, DNAME, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; io.smb2.in.alloc_size = 0; io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE| NTCREATEX_SHARE_ACCESS_DELETE; io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; io.smb2.in.create_options = 0; io.smb2.in.impersonation_level = 0x12345678; io.smb2.in.security_flags = 0; io.smb2.in.fname = fname; io.smb2.in.create_flags = 0; status = smb2_create(tree, tree, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_BAD_IMPERSONATION_LEVEL); smb2_util_close(tree, h); smb2_util_unlink(tree, fname); smb2_deltree(tree, DNAME); return ret; } static bool test_create_acl_file(struct torture_context *tctx, struct smb2_tree *tree) { torture_comment(tctx, "Testing nttrans create with sec_desc on files\n"); return test_create_acl_ext(tctx, tree, false); } static bool test_create_acl_dir(struct torture_context *tctx, struct smb2_tree *tree) { torture_comment(tctx, "Testing nttrans create with sec_desc on directories\n"); return test_create_acl_ext(tctx, tree, true); } #define CHECK_ACCESS_FLAGS(_fh, flags) do { \ union smb_fileinfo _q; \ _q.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; \ _q.access_information.in.file.handle = (_fh); \ status = smb2_getinfo_file(tree, tctx, &_q); \ CHECK_STATUS(status, NT_STATUS_OK); \ if (_q.access_information.out.access_flags != (flags)) { \ torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect access_flags 0x%08x - should be 0x%08x\n", \ __location__, _q.access_information.out.access_flags, (flags)); \ ret = false; \ goto done; \ } \ } while (0) /* * Test creating a file with a NULL DACL. */ static bool test_create_null_dacl(struct torture_context *tctx, struct smb2_tree *tree) { NTSTATUS status; struct smb2_create io; const char *fname = "nulldacl.txt"; bool ret = true; struct smb2_handle handle; union smb_fileinfo q; union smb_setfileinfo s; struct security_descriptor *sd = security_descriptor_initialise(tctx); struct security_acl dacl; torture_comment(tctx, "TESTING SEC_DESC WITH A NULL DACL\n"); smb2_util_unlink(tree, fname); ZERO_STRUCT(io); io.level = RAW_OPEN_SMB2; io.in.create_flags = 0; io.in.desired_access = SEC_STD_READ_CONTROL | SEC_STD_WRITE_DAC | SEC_STD_WRITE_OWNER; io.in.create_options = 0; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; io.in.alloc_size = 0; io.in.create_disposition = NTCREATEX_DISP_CREATE; io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; io.in.security_flags = 0; io.in.fname = fname; io.in.sec_desc = sd; /* XXX create_options ? */ io.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | NTCREATEX_OPTIONS_ASYNC_ALERT | NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | 0x00200000; torture_comment(tctx, "creating a file with a empty sd\n"); status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); handle = io.out.file.handle; torture_comment(tctx, "get the original sd\n"); q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; q.query_secdesc.in.file.handle = handle; q.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; status = smb2_getinfo_file(tree, tctx, &q); CHECK_STATUS(status, NT_STATUS_OK); /* * Testing the created DACL, * the server should add the inherited DACL * when SEC_DESC_DACL_PRESENT isn't specified */ if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { ret = false; torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); } if (q.query_secdesc.out.sd->dacl == NULL) { ret = false; torture_fail_goto(tctx, done, "no DACL has been created on the server!\n"); } torture_comment(tctx, "set NULL DACL\n"); sd->type |= SEC_DESC_DACL_PRESENT; s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; s.set_secdesc.in.file.handle = handle; s.set_secdesc.in.secinfo_flags = SECINFO_DACL; s.set_secdesc.in.sd = sd; status = smb2_setinfo_file(tree, &s); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(tctx, "get the sd\n"); q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; q.query_secdesc.in.file.handle = handle; q.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; status = smb2_getinfo_file(tree, tctx, &q); CHECK_STATUS(status, NT_STATUS_OK); /* Testing the modified DACL */ if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { ret = false; torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); } if (q.query_secdesc.out.sd->dacl != NULL) { ret = false; torture_fail_goto(tctx, done, "DACL has been created on the server!\n"); } io.in.create_disposition = NTCREATEX_DISP_OPEN; torture_comment(tctx, "try open for read control\n"); io.in.desired_access = SEC_STD_READ_CONTROL; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_STD_READ_CONTROL); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "try open for write\n"); io.in.desired_access = SEC_FILE_WRITE_DATA; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_FILE_WRITE_DATA); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "try open for read\n"); io.in.desired_access = SEC_FILE_READ_DATA; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_FILE_READ_DATA); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "try open for generic write\n"); io.in.desired_access = SEC_GENERIC_WRITE; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_RIGHTS_FILE_WRITE); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "try open for generic read\n"); io.in.desired_access = SEC_GENERIC_READ; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_RIGHTS_FILE_READ); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "set DACL with 0 aces\n"); ZERO_STRUCT(dacl); dacl.revision = SECURITY_ACL_REVISION_NT4; dacl.num_aces = 0; sd->dacl = &dacl; s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; s.set_secdesc.in.file.handle = handle; s.set_secdesc.in.secinfo_flags = SECINFO_DACL; s.set_secdesc.in.sd = sd; status = smb2_setinfo_file(tree, &s); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(tctx, "get the sd\n"); q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; q.query_secdesc.in.file.handle = handle; q.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; status = smb2_getinfo_file(tree, tctx, &q); CHECK_STATUS(status, NT_STATUS_OK); /* Testing the modified DACL */ if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { ret = false; torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); } if (q.query_secdesc.out.sd->dacl == NULL) { ret = false; torture_fail_goto(tctx, done, "no DACL has been created on the server!\n"); } if (q.query_secdesc.out.sd->dacl->num_aces != 0) { torture_result(tctx, TORTURE_FAIL, "DACL has %u aces!\n", q.query_secdesc.out.sd->dacl->num_aces); ret = false; goto done; } torture_comment(tctx, "try open for read control\n"); io.in.desired_access = SEC_STD_READ_CONTROL; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_STD_READ_CONTROL); smb2_util_close(tree, io.out.file.handle); torture_comment(tctx, "try open for write => access_denied\n"); io.in.desired_access = SEC_FILE_WRITE_DATA; status = smb2_create(tree, tctx, &io); if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); } else { CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); } torture_comment(tctx, "try open for read => access_denied\n"); io.in.desired_access = SEC_FILE_READ_DATA; status = smb2_create(tree, tctx, &io); if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); } else { CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); } torture_comment(tctx, "try open for generic write => access_denied\n"); io.in.desired_access = SEC_GENERIC_WRITE; status = smb2_create(tree, tctx, &io); if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); } else { CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); } torture_comment(tctx, "try open for generic read => access_denied\n"); io.in.desired_access = SEC_GENERIC_READ; status = smb2_create(tree, tctx, &io); if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); } else { CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); } torture_comment(tctx, "set empty sd\n"); sd->type &= ~SEC_DESC_DACL_PRESENT; sd->dacl = NULL; s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; s.set_secdesc.in.file.handle = handle; s.set_secdesc.in.secinfo_flags = SECINFO_DACL; s.set_secdesc.in.sd = sd; status = smb2_setinfo_file(tree, &s); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(tctx, "get the sd\n"); q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; q.query_secdesc.in.file.handle = handle; q.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; status = smb2_getinfo_file(tree, tctx, &q); CHECK_STATUS(status, NT_STATUS_OK); /* Testing the modified DACL */ if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { ret = false; torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); } if (q.query_secdesc.out.sd->dacl != NULL) { ret = false; torture_fail_goto(tctx, done, "DACL has been created on the server!\n"); } done: smb2_util_close(tree, handle); smb2_util_unlink(tree, fname); smb2_tdis(tree); smb2_logoff(tree->session); return ret; } /* test SMB2 mkdir with OPEN_IF on the same name twice. Must use 2 connections to hit the race. */ static bool test_mkdir_dup(struct torture_context *tctx, struct smb2_tree *tree) { const char *fname = "mkdir_dup"; NTSTATUS status; bool ret = true; union smb_open io; struct smb2_tree **trees; struct smb2_request **requests; union smb_open *ios; int i, num_files = 2; int num_ok = 0; int num_created = 0; int num_existed = 0; torture_comment(tctx, "Testing SMB2 Create Directory with multiple connections\n"); trees = talloc_array(tctx, struct smb2_tree *, num_files); requests = talloc_array(tctx, struct smb2_request *, num_files); ios = talloc_array(tctx, union smb_open, num_files); if ((tctx->ev == NULL) || (trees == NULL) || (requests == NULL) || (ios == NULL)) { torture_fail(tctx, ("talloc failed\n")); ret = false; goto done; } tree->session->transport->options.request_timeout = 60; for (i=0; isession->transport->options.request_timeout = 60; } /* cleanup */ smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, fname); /* base ntcreatex parms */ ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; io.smb2.in.alloc_size = 0; io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE| NTCREATEX_SHARE_ACCESS_DELETE; io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; io.smb2.in.security_flags = 0; io.smb2.in.fname = fname; io.smb2.in.create_flags = 0; for (i=0; istate < SMB2_REQUEST_DONE) { unreplied = true; break; } status = smb2_create_recv(requests[i], tctx, &(ios[i].smb2)); if (NT_STATUS_IS_OK(status)) { num_ok += 1; if (ios[i].smb2.out.create_action == NTCREATEX_ACTION_CREATED) { num_created++; } if (ios[i].smb2.out.create_action == NTCREATEX_ACTION_EXISTED) { num_existed++; } } else { torture_fail(tctx, talloc_asprintf(tctx, "File %d returned status %s\n", i, nt_errstr(status))); } requests[i] = NULL; } if (!unreplied) { break; } if (tevent_loop_once(tctx->ev) != 0) { torture_fail(tctx, "tevent_loop_once failed\n"); ret = false; goto done; } } if (num_ok != 2) { torture_fail(tctx, talloc_asprintf(tctx, "num_ok == %d\n", num_ok)); ret = false; } if (num_created != 1) { torture_fail(tctx, talloc_asprintf(tctx, "num_created == %d\n", num_created)); ret = false; } if (num_existed != 1) { torture_fail(tctx, talloc_asprintf(tctx, "num_existed == %d\n", num_existed)); ret = false; } done: smb2_deltree(tree, fname); return ret; } /* test directory creation with an initial allocation size > 0 */ static bool test_dir_alloc_size(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; const char *dname = DNAME "\\torture_alloc_size.dir"; NTSTATUS status; struct smb2_create c; struct smb2_handle h1 = {{0}}, h2; torture_comment(tctx, "Checking initial allocation size on directories\n"); smb2_deltree(tree, dname); status = torture_smb2_testdir(tree, DNAME, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed"); ZERO_STRUCT(c); c.in.create_disposition = NTCREATEX_DISP_CREATE; c.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; c.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; c.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; c.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; c.in.fname = dname; /* * An insanely large value so we can check the value is * ignored: Samba either returns 0 (current behaviour), or, * once vfswrap_get_alloc_size() is fixed to allow retrieving * the allocated size for directories, returns * smb_roundup(..., stat.st_size) which would be 1 MB by * default. * * Windows returns 0 for empty directories, once directories * have a few entries it starts replying with values > 0. */ c.in.alloc_size = 1024*1024*1024; status = smb2_create(tree, tctx, &c); h2 = c.out.file.handle; torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "dir create with initial alloc size failed"); smb2_util_close(tree, h2); torture_comment(tctx, "Got directory alloc size: %ju\n", (uintmax_t)c.out.alloc_size); /* * See above for the rational for this test */ if (c.out.alloc_size > 1024*1024) { torture_fail_goto(tctx, done, talloc_asprintf(tctx, "bad alloc size: %ju", (uintmax_t)c.out.alloc_size)); } done: if (!smb2_util_handle_empty(h1)) { smb2_util_close(tree, h1); } smb2_deltree(tree, DNAME); return ret; } static bool test_twrp_write(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create io; struct smb2_handle h1 = {{0}}; NTSTATUS status; bool ret = true; char *p = NULL; struct tm tm; time_t t; uint64_t nttime; const char *file = NULL; const char *snapshot = NULL; uint32_t expected_access; union smb_fileinfo getinfo; union smb_setfileinfo setinfo; struct security_descriptor *sd = NULL, *sd_orig = NULL; const char *owner_sid = NULL; struct create_disps_tests { const char *file; uint32_t create_disposition; uint32_t create_options; NTSTATUS expected_status; }; struct create_disps_tests *cd_test = NULL; file = torture_setting_string(tctx, "twrp_file", NULL); if (file == NULL) { torture_skip(tctx, "missing 'twrp_file' option\n"); } snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); if (snapshot == NULL) { torture_skip(tctx, "missing 'twrp_snapshot' option\n"); } torture_comment(tctx, "Testing timewarp (%s) (%s)\n", file, snapshot); setenv("TZ", "GMT", 1); /* strptime does not set tm.tm_isdst but mktime assumes DST is in * effect if it is greater than 1. */ ZERO_STRUCT(tm); p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); t = mktime(&tm); unix_to_nt_time(&nttime, t); io = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_DATA, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = file, .in.query_maximal_access = true, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); smb2_util_close(tree, io.out.file.handle); expected_access = SEC_RIGHTS_FILE_ALL & ~(SEC_FILE_EXECUTE | SEC_DIR_DELETE_CHILD); torture_assert_int_equal_goto(tctx, io.out.maximal_access & expected_access, expected_access, ret, done, "Bad access\n"); { /* * Test create dispositions */ struct create_disps_tests cd_tests[] = { { .file = file, .create_disposition = NTCREATEX_DISP_OPEN, .expected_status = NT_STATUS_OK, }, { .file = file, .create_disposition = NTCREATEX_DISP_OPEN_IF, .expected_status = NT_STATUS_OK, }, { .file = file, .create_disposition = NTCREATEX_DISP_OVERWRITE, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = file, .create_disposition = NTCREATEX_DISP_OVERWRITE_IF, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = file, .create_disposition = NTCREATEX_DISP_SUPERSEDE, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newfile", .create_disposition = NTCREATEX_DISP_OPEN_IF, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newfile", .create_disposition = NTCREATEX_DISP_OVERWRITE_IF, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newfile", .create_disposition = NTCREATEX_DISP_CREATE, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newfile", .create_disposition = NTCREATEX_DISP_SUPERSEDE, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newdir", .create_disposition = NTCREATEX_DISP_OPEN_IF, .create_options = NTCREATEX_OPTIONS_DIRECTORY, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = "newdir", .create_disposition = NTCREATEX_DISP_CREATE, .create_options = NTCREATEX_OPTIONS_DIRECTORY, .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED, }, { .file = NULL, }, }; for (cd_test = &cd_tests[0]; cd_test->file != NULL; cd_test++) { io = (struct smb2_create) { .in.fname = cd_test->file, .in.create_disposition = cd_test->create_disposition, .in.create_options = cd_test->create_options, .in.desired_access = SEC_FILE_READ_DATA, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_equal_goto( tctx, status, cd_test->expected_status, ret, done, "Bad status\n"); } } io = (struct smb2_create) { .in.desired_access = expected_access, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = file, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h1 = io.out.file.handle; status = smb2_util_write(tree, h1, "123", 0, 3); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_MEDIA_WRITE_PROTECTED, ret, done, "smb2_create\n"); /* * Verify access mask */ ZERO_STRUCT(getinfo); getinfo.generic.level = RAW_FILEINFO_ACCESS_INFORMATION; getinfo.generic.in.file.handle = h1; status = smb2_getinfo_file(tree, tree, &getinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file\n"); torture_assert_int_equal_goto( tctx, getinfo.access_information.out.access_flags, expected_access, ret, done, "Bad access mask\n"); /* * Check we can't set various things */ ZERO_STRUCT(getinfo); getinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; getinfo.query_secdesc.in.file.handle = h1; getinfo.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; status = smb2_getinfo_file(tree, tctx, &getinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file\n"); sd_orig = getinfo.query_secdesc.out.sd; owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, owner_sid, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, 0, NULL); /* Try to set ACL */ ZERO_STRUCT(setinfo); setinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; setinfo.set_secdesc.in.file.handle = h1; setinfo.set_secdesc.in.secinfo_flags = SECINFO_DACL; setinfo.set_secdesc.in.sd = sd; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_MEDIA_WRITE_PROTECTED, ret, done, "smb2_setinfo_file\n"); /* Try to delete */ ZERO_STRUCT(setinfo); setinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; setinfo.disposition_info.in.delete_on_close = 1; setinfo.generic.in.file.handle = h1; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_MEDIA_WRITE_PROTECTED, ret, done, "smb2_setinfo_file\n"); ZERO_STRUCT(setinfo); setinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN; setinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; setinfo.generic.in.file.handle = h1; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_MEDIA_WRITE_PROTECTED, ret, done, "smb2_setinfo_file\n"); /* Try to truncate */ ZERO_STRUCT(setinfo); setinfo.generic.level = SMB_SFILEINFO_END_OF_FILE_INFORMATION; setinfo.generic.in.file.handle = h1; setinfo.end_of_file_info.in.size = 0x100000; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_MEDIA_WRITE_PROTECTED, ret, done, "smb2_setinfo_file\n"); /* Try to set a hardlink */ ZERO_STRUCT(setinfo); setinfo.generic.level = RAW_SFILEINFO_LINK_INFORMATION; setinfo.generic.in.file.handle = h1; setinfo.link_information.in.new_name = "hardlink"; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_NOT_SAME_DEVICE, ret, done, "smb2_setinfo_file\n"); /* Try to rename */ ZERO_STRUCT(setinfo); setinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; setinfo.rename_information.in.file.handle = h1; setinfo.rename_information.in.new_name = "renamed"; status = smb2_setinfo_file(tree, &setinfo); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_NOT_SAME_DEVICE, ret, done, "smb2_setinfo_file\n"); smb2_util_close(tree, h1); ZERO_STRUCT(h1); done: if (!smb2_util_handle_empty(h1)) { smb2_util_close(tree, h1); } return ret; } static bool test_twrp_stream(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create io; NTSTATUS status; bool ret = true; char *p = NULL; struct tm tm; time_t t; uint64_t nttime; const char *file = NULL; const char *stream = NULL; const char *snapshot = NULL; int stream_size; char *path = NULL; uint8_t *buf = NULL; struct smb2_handle h1 = {{0}}; struct smb2_read r; file = torture_setting_string(tctx, "twrp_file", NULL); if (file == NULL) { torture_skip(tctx, "missing 'twrp_file' option\n"); } stream = torture_setting_string(tctx, "twrp_stream", NULL); if (stream == NULL) { torture_skip(tctx, "missing 'twrp_stream' option\n"); } snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); if (snapshot == NULL) { torture_skip(tctx, "missing 'twrp_snapshot' option\n"); } stream_size = torture_setting_int(tctx, "twrp_stream_size", 0); if (stream_size == 0) { torture_skip(tctx, "missing 'twrp_stream_size' option\n"); } torture_comment(tctx, "Testing timewarp on stream (%s) (%s)\n", file, snapshot); path = talloc_asprintf(tree, "%s:%s", file, stream); torture_assert_not_null_goto(tctx, path, ret, done, "path\n"); buf = talloc_zero_array(tree, uint8_t, stream_size); torture_assert_not_null_goto(tctx, buf, ret, done, "buf\n"); setenv("TZ", "GMT", 1); /* strptime does not set tm.tm_isdst but mktime assumes DST is in * effect if it is greater than 1. */ ZERO_STRUCT(tm); p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); t = mktime(&tm); unix_to_nt_time(&nttime, t); io = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_DATA, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = path, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h1 = io.out.file.handle; r = (struct smb2_read) { .in.file.handle = h1, .in.length = stream_size, .in.offset = 0, }; status = smb2_read(tree, tree, &r); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); smb2_util_close(tree, h1); done: return ret; } static bool test_twrp_openroot(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create io; NTSTATUS status; bool ret = true; char *p = NULL; struct tm tm; time_t t; uint64_t nttime; const char *snapshot = NULL; snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); if (snapshot == NULL) { torture_skip(tctx, "missing 'twrp_snapshot' option\n"); } torture_comment(tctx, "Testing open of root of " "share with timewarp (%s)\n", snapshot); setenv("TZ", "GMT", 1); /* strptime does not set tm.tm_isdst but mktime assumes DST is in * effect if it is greater than 1. */ ZERO_STRUCT(tm); p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); t = mktime(&tm); unix_to_nt_time(&nttime, t); io = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_DATA, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = "", .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); smb2_util_close(tree, io.out.file.handle); done: return ret; } static bool test_twrp_listdir(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create create; struct smb2_handle h = {{0}}; struct smb2_find find; unsigned int count; union smb_search_data *d; char *p = NULL; struct tm tm; time_t t; uint64_t nttime; const char *snapshot = NULL; uint64_t normal_fileid; uint64_t snapshot_fileid; NTSTATUS status; bool ret = true; snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); if (snapshot == NULL) { torture_fail(tctx, "missing 'twrp_snapshot' option\n"); } torture_comment(tctx, "Testing File-Ids of directory listing " "with timewarp (%s)\n", snapshot); setenv("TZ", "GMT", 1); /* strptime does not set tm.tm_isdst but mktime assumes DST is in * effect if it is greater than 1. */ ZERO_STRUCT(tm); p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); t = mktime(&tm); unix_to_nt_time(&nttime, t); /* * 1: Query the file's File-Id */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_DATA, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = "subdir/hardlink", .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); smb2_util_close(tree, create.out.file.handle); normal_fileid = BVAL(&create.out.on_disk_id, 0); /* * 2: check directory listing of the file returns same File-Id */ create = (struct smb2_create) { .in.desired_access = SEC_DIR_LIST, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = "subdir", .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h = create.out.file.handle; find = (struct smb2_find) { .in.file.handle = h, .in.pattern = "*", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, }; status = smb2_find_level(tree, tree, &find, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); smb2_util_close(tree, h); torture_assert_int_equal_goto(tctx, count, 3, ret, done, "Bad count\n"); torture_assert_str_equal_goto(tctx, d[2].id_both_directory_info.name.s, "hardlink", ret, done, "bad name"); torture_assert_u64_equal_goto(tctx, d[2].id_both_directory_info.file_id, normal_fileid, ret, done, "bad fileid\n"); /* * 3: Query File-Id of snapshot of the file and check the File-Id is * different compared to the basefile */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_DATA, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = "subdir/hardlink", .in.query_on_disk_id = true, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); smb2_util_close(tree, create.out.file.handle); snapshot_fileid = BVAL(&create.out.on_disk_id, 0); /* * 4: List directory of the snapshot and check the File-Id returned here * is the same as in 3. */ create = (struct smb2_create) { .in.desired_access = SEC_DIR_LIST, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = "subdir", .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.timewarp = nttime, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h = create.out.file.handle; find = (struct smb2_find) { .in.file.handle = h, .in.pattern = "*", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, }; status = smb2_find_level(tree, tree, &find, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); smb2_util_close(tree, h); torture_assert_int_equal_goto(tctx, count, 3, ret, done, "Bad count\n"); torture_assert_str_equal_goto(tctx, d[2].id_both_directory_info.name.s, "hardlink", ret, done, "bad name"); torture_assert_u64_equal_goto(tctx, snapshot_fileid, d[2].id_both_directory_info.file_id, ret, done, "bad fileid\n"); done: return ret; } static bool test_fileid(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = DNAME "\\foo"; const char *sname = DNAME "\\foo:bar"; struct smb2_handle testdirh; struct smb2_handle h1; struct smb2_create create; union smb_fileinfo finfo; union smb_setfileinfo sinfo; struct smb2_find f; unsigned int count; union smb_search_data *d; uint64_t expected_fileid; uint64_t returned_fileid; NTSTATUS status; bool ret = true; smb2_deltree(tree, DNAME); status = torture_smb2_testdir(tree, DNAME, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed\n"); /* * Initial create with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN_IF, .in.fname = fname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; expected_fileid = BVAL(&create.out.on_disk_id, 0); /* * Getinfo the File-ID on the just opened handle */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Open existing with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = fname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the just opened handle */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Overwrite with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OVERWRITE, .in.fname = fname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the open with overwrite handle */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Do some modifications on the basefile (IO, setinfo), verifying * File-ID after each step. */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = fname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); sinfo = (union smb_setfileinfo) { .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, .basic_info.in.file.handle = h1, }; unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); status = smb2_setinfo_file(tree, &sinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_setinfo_file failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Create stream, check the stream's File-ID, should be the same as the * base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_CREATE, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the created stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Open stream, check the stream's File-ID, should be the same as the * base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the opened stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Overwrite stream, check the stream's File-ID, should be the same as * the base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OVERWRITE, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the overwritten stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Do some modifications on the stream (IO, setinfo), verifying File-ID * after each step. */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); sinfo = (union smb_setfileinfo) { .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, .basic_info.in.file.handle = h1, }; unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); status = smb2_setinfo_file(tree, &sinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_setinfo_file failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Final open of the basefile with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = fname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Final Getinfo checking File-ID */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Final list directory, verifying the operations on basefile and stream * didn't modify the base file metadata. */ f = (struct smb2_find) { .in.file.handle = testdirh, .in.pattern = "foo", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, .in.continue_flags = SMB2_CONTINUE_FLAG_RESTART, }; status = smb2_find_level(tree, tree, &f, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); torture_assert_u64_equal_goto(tctx, d->id_both_directory_info.file_id, expected_fileid, ret, done, "bad fileid\n"); done: smb2_util_close(tree, testdirh); smb2_deltree(tree, DNAME); talloc_free(mem_ctx); return ret; } static bool test_fileid_dir(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *dname = DNAME "\\foo"; const char *sname = DNAME "\\foo:bar"; struct smb2_handle testdirh; struct smb2_handle h1; struct smb2_create create; union smb_fileinfo finfo; union smb_setfileinfo sinfo; struct smb2_find f; unsigned int count; union smb_search_data *d; uint64_t expected_fileid; uint64_t returned_fileid; NTSTATUS status; bool ret = true; smb2_deltree(tree, DNAME); status = torture_smb2_testdir(tree, DNAME, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed\n"); /* * Initial directory create with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.create_disposition = NTCREATEX_DISP_OPEN_IF, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.fname = dname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; expected_fileid = BVAL(&create.out.on_disk_id, 0); /* * Getinfo the File-ID on the just opened handle */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Open existing directory with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.fname = dname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the just opened handle */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Create stream, check the stream's File-ID, should be the same as the * base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_CREATE, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the created stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Open stream, check the stream's File-ID, should be the same as the * base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the opened stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Overwrite stream, check the stream's File-ID, should be the same as * the base file (sic!, tested against Windows). */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OVERWRITE, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Getinfo the File-ID on the overwritten stream */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Do some modifications on the stream (IO, setinfo), verifying File-ID * after each step. */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = sname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); sinfo = (union smb_setfileinfo) { .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, .basic_info.in.file.handle = h1, }; unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); status = smb2_setinfo_file(tree, &sinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_setinfo_file failed\n"); finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Final open of the directory with QFID */ create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = dname, .in.query_on_disk_id = true, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test file could not be created\n"); h1 = create.out.file.handle; returned_fileid = BVAL(&create.out.on_disk_id, 0); torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, ret, done, "bad fileid\n"); /* * Final Getinfo checking File-ID */ finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, expected_fileid, ret, done, "bad fileid\n"); /* * Final list directory, verifying the operations on basefile and stream * didn't modify the base file metadata. */ f = (struct smb2_find) { .in.file.handle = testdirh, .in.pattern = "foo", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, .in.continue_flags = SMB2_CONTINUE_FLAG_RESTART, }; status = smb2_find_level(tree, tree, &f, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); torture_assert_u64_equal_goto(tctx, d->id_both_directory_info.file_id, expected_fileid, ret, done, "bad fileid\n"); done: smb2_util_close(tree, testdirh); smb2_deltree(tree, DNAME); talloc_free(mem_ctx); return ret; } static bool test_fileid_unique_object( struct torture_context *tctx, struct smb2_tree *tree, unsigned int num_objs, bool create_dirs) { TALLOC_CTX *mem_ctx = talloc_new(tctx); char *fname = NULL; struct smb2_handle testdirh; struct smb2_handle h1; struct smb2_create create; unsigned int i; uint64_t fileid_array[num_objs]; NTSTATUS status; bool ret = true; smb2_deltree(tree, DNAME); status = torture_smb2_testdir(tree, DNAME, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "test_fileid_unique failed\n"); smb2_util_close(tree, testdirh); /* Create num_obj files as rapidly as we can. */ for (i = 0; i < num_objs; i++) { fname = talloc_asprintf(mem_ctx, "%s\\testfile.%u", DNAME, i); torture_assert_goto(tctx, fname != NULL, ret, done, "talloc failed\n"); create = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_ATTRIBUTE, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_CREATE, .in.fname = fname, }; if (create_dirs) { create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; create.in.create_options = FILE_DIRECTORY_FILE; } status = smb2_create(tree, tctx, &create); if (!NT_STATUS_IS_OK(status)) { torture_fail(tctx, talloc_asprintf(tctx, "test file %s could not be created\n", fname)); TALLOC_FREE(fname); ret = false; goto done; } h1 = create.out.file.handle; smb2_util_close(tree, h1); TALLOC_FREE(fname); } /* * Get the file ids. */ for (i = 0; i < num_objs; i++) { union smb_fileinfo finfo; fname = talloc_asprintf(mem_ctx, "%s\\testfile.%u", DNAME, i); torture_assert_goto(tctx, fname != NULL, ret, done, "talloc failed\n"); create = (struct smb2_create) { .in.desired_access = SEC_FILE_READ_ATTRIBUTE, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = fname, }; if (create_dirs) { create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; create.in.create_options = FILE_DIRECTORY_FILE; } status = smb2_create(tree, tctx, &create); if (!NT_STATUS_IS_OK(status)) { torture_fail(tctx, talloc_asprintf(tctx, "test file %s could not " "be opened: %s\n", fname, nt_errstr(status))); TALLOC_FREE(fname); ret = false; goto done; } h1 = create.out.file.handle; finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, .generic.in.file.handle = h1, }; status = smb2_getinfo_file(tree, tctx, &finfo); if (!NT_STATUS_IS_OK(status)) { torture_fail(tctx, talloc_asprintf(tctx, "failed to get fileid for " "test file %s: %s\n", fname, nt_errstr(status))); TALLOC_FREE(fname); ret = false; goto done; } smb2_util_close(tree, h1); fileid_array[i] = finfo.all_info2.out.file_id; TALLOC_FREE(fname); } /* All returned fileids must be unique. 100 is small so brute force. */ for (i = 0; i < num_objs - 1; i++) { unsigned int j; for (j = i + 1; j < num_objs; j++) { if (fileid_array[i] == fileid_array[j]) { torture_fail(tctx, talloc_asprintf(tctx, "fileid %u == fileid %u (0x%"PRIu64")\n", i, j, fileid_array[i])); ret = false; goto done; } } } done: smb2_util_close(tree, testdirh); smb2_deltree(tree, DNAME); talloc_free(mem_ctx); return ret; } static bool test_fileid_unique( struct torture_context *tctx, struct smb2_tree *tree) { return test_fileid_unique_object(tctx, tree, 100, false); } static bool test_fileid_unique_dir( struct torture_context *tctx, struct smb2_tree *tree) { return test_fileid_unique_object(tctx, tree, 100, true); } static bool test_dosattr_tmp_dir(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create c; struct smb2_handle h1 = {{0}}; const char *fname = DNAME; smb2_deltree(tree, fname); smb2_util_rmdir(tree, fname); c = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_DIR_ALL, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.create_disposition = NTCREATEX_DISP_OPEN_IF, .in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.fname = DNAME, }; status = smb2_create(tree, tctx, &c); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h1 = c.out.file.handle; /* Try to set temporary attribute on directory */ SET_ATTRIB(FILE_ATTRIBUTE_TEMPORARY); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, ret, done, "Unexpected setinfo result\n"); done: if (!smb2_util_handle_empty(h1)) { smb2_util_close(tree, h1); } smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); return ret; } /* test opening quota fakefile handle and returned attributes */ static bool test_smb2_open_quota_fake_file(struct torture_context *tctx, struct smb2_tree *tree) { const char *fname = "$Extend\\$Quota:$Q:$INDEX_ALLOCATION"; struct smb2_create create; struct smb2_handle h = {{0}}; NTSTATUS status; bool ret = true; create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_FILE_READ, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = fname, }; status = smb2_create(tree, tree, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); h = create.out.file.handle; torture_assert_u64_equal_goto(tctx, create.out.file_attr, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ARCHIVE, ret, done, "Wrong attributes\n"); torture_assert_u64_equal_goto(tctx, create.out.create_time, 0, ret, done, "create_time is not 0\n"); torture_assert_u64_equal_goto(tctx, create.out.access_time, 0, ret, done, "access_time is not 0\n"); torture_assert_u64_equal_goto(tctx, create.out.write_time, 0, ret, done, "write_time is not 0\n"); torture_assert_u64_equal_goto(tctx, create.out.change_time, 0, ret, done, "change_time is not 0\n"); done: smb2_util_close(tree, h); return ret; } /** Find Maximum Path Length */ static bool generate_path(const size_t len, char *buffer, const size_t buf_len) { size_t i; if (len >= buf_len) { return false; } for (i = 0; i < len ; i++) { buffer[i] = (char)(i % 10) + 48; } buffer[i] = '\0'; return true; } static bool test_path_length_test(struct torture_context *tctx, struct smb2_tree *tree) { const size_t max_name = 2048; char *name = talloc_array(tctx, char, max_name); struct smb2_handle fh = {{0}}; size_t length = 128; size_t max_file_name = 0; size_t max_path_length = 0; char *path_ok = NULL; char *path_next = NULL; char *topdir = NULL; bool is_interactive = torture_setting_bool(tctx, "interactive", false); NTSTATUS status; bool ret = true; if (!is_interactive) { torture_result(tctx, TORTURE_SKIP, "Interactive Test: Skipping... " "(enable with --interactive)\n"); return ret; } torture_comment(tctx, "Testing filename and path lengths\n"); /* Find Longest File Name */ for (length = 128; length < max_name; length++) { if (!generate_path(length, name, max_name)) { torture_result(tctx, TORTURE_FAIL, "Failed to generate path."); return false; } status = torture_smb2_testfile(tree, name, &fh); if (!NT_STATUS_IS_OK(status)) { break; } smb2_util_close(tree, fh); smb2_util_unlink(tree, name); max_file_name = length; } torture_assert_int_not_equal_goto(tctx, length, max_name, ret, done, "Name too big\n"); torture_comment(tctx, "Max file name length: %zu\n", max_file_name); /* Remove one char that caused the failure above */ name[max_file_name] = '\0'; path_ok = talloc_strdup(tree, name); torture_assert_not_null_goto(tctx, path_ok, ret, done, "talloc_strdup failed\n"); topdir = talloc_strdup(tree, name); torture_assert_not_null_goto(tctx, topdir, ret, done, "talloc_strdup failed\n"); status = smb2_util_mkdir(tree, path_ok); if (!NT_STATUS_IS_OK(status)) { torture_comment(tctx, "mkdir [%s] failed: %s\n", path_ok, nt_errstr(status)); torture_result(tctx, TORTURE_FAIL, "Initial mkdir failed"); return false; } while (true) { path_next = talloc_asprintf(tctx, "%s\\%s", path_ok, name); torture_assert_not_null_goto(tctx, path_next, ret, done, "talloc_asprintf failed\n"); status = smb2_util_mkdir(tree, path_next); if (!NT_STATUS_IS_OK(status)) { break; } path_ok = path_next; } for (length = 1; length < max_name; length++) { if (!generate_path(length, name, max_name)) { torture_result(tctx, TORTURE_FAIL, "Failed to generate path."); return false; } path_next = talloc_asprintf(tctx, "%s\\%s", path_ok, name); torture_assert_not_null_goto(tctx, path_next, ret, done, "talloc_asprintf failed\n"); status = torture_smb2_testfile(tree, path_next, &fh); if (!NT_STATUS_IS_OK(status)) { break; } smb2_util_close(tree, fh); path_ok = path_next; } max_path_length = talloc_array_length(path_ok); torture_comment(tctx, "Max path name length: %zu\n", max_path_length); done: return ret; } /* basic testing of SMB2 read */ struct torture_suite *torture_smb2_create_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "create"); torture_suite_add_1smb2_test(suite, "gentest", test_create_gentest); torture_suite_add_1smb2_test(suite, "blob", test_create_blob); torture_suite_add_1smb2_test(suite, "open", test_smb2_open); torture_suite_add_1smb2_test(suite, "brlocked", test_smb2_open_brlocked); torture_suite_add_1smb2_test(suite, "multi", test_smb2_open_multi); torture_suite_add_1smb2_test(suite, "delete", test_smb2_open_for_delete); torture_suite_add_1smb2_test(suite, "leading-slash", test_smb2_leading_slash); torture_suite_add_1smb2_test(suite, "impersonation", test_smb2_impersonation_level); torture_suite_add_1smb2_test(suite, "aclfile", test_create_acl_file); 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, "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); torture_suite_add_1smb2_test(suite, "path-length", test_path_length_test); torture_suite_add_1smb2_test(suite, "bench-path-contention-shared", test_smb2_bench_path_contention_shared); suite->description = talloc_strdup(suite, "SMB2-CREATE tests"); return suite; } struct torture_suite *torture_smb2_twrp_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "twrp"); torture_suite_add_1smb2_test(suite, "write", test_twrp_write); torture_suite_add_1smb2_test(suite, "stream", test_twrp_stream); torture_suite_add_1smb2_test(suite, "openroot", test_twrp_openroot); torture_suite_add_1smb2_test(suite, "listdir", test_twrp_listdir); suite->description = talloc_strdup(suite, "SMB2-TWRP tests"); return suite; } /* basic testing of SMB2 File-IDs */ struct torture_suite *torture_smb2_fileid_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "fileid"); torture_suite_add_1smb2_test(suite, "fileid", test_fileid); torture_suite_add_1smb2_test(suite, "fileid-dir", test_fileid_dir); torture_suite_add_1smb2_test(suite, "unique", test_fileid_unique); torture_suite_add_1smb2_test(suite, "unique-dir", test_fileid_unique_dir); suite->description = talloc_strdup(suite, "SMB2-FILEID tests"); return suite; } static bool test_no_stream(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create c; NTSTATUS status; bool ret = true; const char *names[] = { "test_no_stream::$DATA", "test_no_stream::foooooooooooo", "test_no_stream:stream", "test_no_stream:stream:$DATA", NULL }; int i; for (i = 0; names[i] != NULL; i++) { c = (struct smb2_create) { .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.fname = names[i], }; status = smb2_create(tree, tctx, &c); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) { torture_comment( tctx, "Expected NT_STATUS_OBJECT_NAME_INVALID, " "got %s, name: '%s'\n", nt_errstr(status), names[i]); torture_fail_goto(tctx, done, "Bad create result\n"); } } done: return ret; } struct torture_suite *torture_smb2_create_no_streams_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "create_no_streams"); torture_suite_add_1smb2_test(suite, "no_stream", test_no_stream); suite->description = talloc_strdup(suite, "SMB2-CREATE stream test on share without streams support"); return suite; }