/* Unix SMB/CIFS implementation. SMB torture tester - deny mode scanning functions Copyright (C) Andrew Tridgell 2001 Copyright (C) David Mulder 2019 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 "system/filesys.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "libcli/security/security.h" #include "torture/util.h" #include "torture/smb2/proto.h" #define MAXIMUM_ALLOWED_FILE "torture_maximum_allowed" static bool torture_smb2_maximum_allowed(struct torture_context *tctx, struct smb2_tree *tree) { struct security_descriptor *sd = NULL, *sd_orig = NULL; struct smb2_create io = {0}; TALLOC_CTX *mem_ctx = NULL; struct smb2_handle fnum = {{0}}; int i; bool ret = true; NTSTATUS status; union smb_fileinfo q; const char *owner_sid = NULL; bool has_restore_privilege, has_backup_privilege, has_system_security_privilege; mem_ctx = talloc_init("torture_maximum_allowed"); torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc allocation failed\n"); if (!torture_setting_bool(tctx, "sacl_support", true)) torture_warning(tctx, "Skipping SACL related tests!\n"); sd = security_descriptor_dacl_create(mem_ctx, 0, NULL, NULL, SID_NT_AUTHENTICATED_USERS, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_RIGHTS_FILE_READ, 0, NULL); torture_assert_goto(tctx, sd != NULL, ret, done, "security descriptor creation failed\n"); /* Blank slate */ smb2_util_unlink(tree, MAXIMUM_ALLOWED_FILE); /* create initial file with restrictive SD */ io.in.desired_access = SEC_RIGHTS_FILE_ALL; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_CREATE; io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; io.in.fname = MAXIMUM_ALLOWED_FILE; io.in.sec_desc = sd; status = smb2_create(tree, mem_ctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", nt_errstr(status), nt_errstr(NT_STATUS_OK))); fnum = io.out.file.handle; /* the correct answers for this test depends on whether the user has restore privileges. To find that out we first need to know our SID - get it from the owner_sid of the file we just created */ q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; q.query_secdesc.in.file.handle = fnum; q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; status = smb2_getinfo_file(tree, tctx, &q); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", nt_errstr(status), nt_errstr(NT_STATUS_OK))); sd_orig = q.query_secdesc.out.sd; owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); status = torture_smb2_check_privilege(tree, owner_sid, sec_privilege_name(SEC_PRIV_RESTORE)); has_restore_privilege = NT_STATUS_IS_OK(status); torture_comment(tctx, "Checked SEC_PRIV_RESTORE for %s - %s\n", owner_sid, has_restore_privilege?"Yes":"No"); status = torture_smb2_check_privilege(tree, owner_sid, sec_privilege_name(SEC_PRIV_BACKUP)); has_backup_privilege = NT_STATUS_IS_OK(status); torture_comment(tctx, "Checked SEC_PRIV_BACKUP for %s - %s\n", owner_sid, has_backup_privilege?"Yes":"No"); status = torture_smb2_check_privilege(tree, owner_sid, sec_privilege_name(SEC_PRIV_SECURITY)); has_system_security_privilege = NT_STATUS_IS_OK(status); torture_comment(tctx, "Checked SEC_PRIV_SECURITY for %s - %s\n", owner_sid, has_system_security_privilege?"Yes":"No"); smb2_util_close(tree, fnum); for (i = 0; i < 32; i++) { uint32_t mask = SEC_FLAG_MAXIMUM_ALLOWED | (1u << i); /* * SEC_GENERIC_EXECUTE is a complete subset of * SEC_GENERIC_READ when mapped to specific bits, * so we need to include it in the basic OK mask. */ uint32_t ok_mask = SEC_RIGHTS_FILE_READ | SEC_GENERIC_READ | SEC_GENERIC_EXECUTE | SEC_STD_DELETE | SEC_STD_WRITE_DAC; /* * Now SEC_RIGHTS_PRIV_RESTORE and SEC_RIGHTS_PRIV_BACKUP * don't include any generic bits (they're used directly * in the fileserver where the generic bits have already * been mapped into file specific bits) we need to add the * generic bits to the ok_mask when we have these privileges. */ if (has_restore_privilege) { ok_mask |= SEC_RIGHTS_PRIV_RESTORE|SEC_GENERIC_WRITE; } if (has_backup_privilege) { ok_mask |= SEC_RIGHTS_PRIV_BACKUP|SEC_GENERIC_READ; } if (has_system_security_privilege) { ok_mask |= SEC_FLAG_SYSTEM_SECURITY; } /* Skip all SACL related tests. */ if ((!torture_setting_bool(tctx, "sacl_support", true)) && (mask & SEC_FLAG_SYSTEM_SECURITY)) continue; io = (struct smb2_create){0}; io.in.desired_access = mask; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OPEN; io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; io.in.fname = MAXIMUM_ALLOWED_FILE; status = smb2_create(tree, mem_ctx, &io); if (mask & ok_mask || mask == SEC_FLAG_MAXIMUM_ALLOWED) { torture_assert_ntstatus_ok_goto(tctx, status, ret, done, talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", nt_errstr(status), nt_errstr(NT_STATUS_OK))); } else { if (mask & SEC_FLAG_SYSTEM_SECURITY) { torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_PRIVILEGE_NOT_HELD, ret, done, talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", nt_errstr(status), nt_errstr(NT_STATUS_PRIVILEGE_NOT_HELD))); } else { torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, ret, done, talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", nt_errstr(status), nt_errstr(NT_STATUS_ACCESS_DENIED))); } } fnum = io.out.file.handle; smb2_util_close(tree, fnum); } done: smb2_util_unlink(tree, MAXIMUM_ALLOWED_FILE); talloc_free(mem_ctx); return ret; } static bool torture_smb2_read_only_file(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_create c; struct smb2_handle h = {{0}}; bool ret = true; NTSTATUS status; smb2_deltree(tree, MAXIMUM_ALLOWED_FILE); c = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_FILE_ALL, .in.file_attributes = FILE_ATTRIBUTE_READONLY, .in.create_disposition = NTCREATEX_DISP_CREATE, .in.fname = MAXIMUM_ALLOWED_FILE, }; status = smb2_create(tree, tctx, &c); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); h = c.out.file.handle; smb2_util_close(tree, h); ZERO_STRUCT(h); c = (struct smb2_create) { .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, .in.file_attributes = FILE_ATTRIBUTE_READONLY, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.fname = MAXIMUM_ALLOWED_FILE, }; status = smb2_create(tree, tctx, &c); torture_assert_ntstatus_ok_goto( tctx, status, ret, done, "Failed to open READ-ONLY file with SEC_FLAG_MAXIMUM_ALLOWED\n"); h = c.out.file.handle; smb2_util_close(tree, h); ZERO_STRUCT(h); done: if (!smb2_util_handle_empty(h)) { smb2_util_close(tree, h); } smb2_deltree(tree, MAXIMUM_ALLOWED_FILE); return ret; } struct torture_suite *torture_smb2_max_allowed(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "maximum_allowed"); torture_suite_add_1smb2_test(suite, "maximum_allowed", torture_smb2_maximum_allowed); torture_suite_add_1smb2_test(suite, "read_only", torture_smb2_read_only_file); return suite; }