// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/win/security_util.h" #include #include #include #include #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/strings/stringprintf.h" #include "base/win/scoped_handle.h" #include "base/win/scoped_localalloc.h" #include "base/win/sid.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace win { namespace { constexpr wchar_t kBaseDacl[] = L"D:P(A;;FA;;;WD)"; constexpr wchar_t kTest1Dacl[] = L"D:PAI(A;;FR;;;AU)(A;;FA;;;WD)"; constexpr wchar_t kTest2Dacl[] = L"D:PAI(A;;FA;;;BA)(A;;FA;;;AU)(A;;FA;;;WD)"; constexpr wchar_t kTest1DenyDacl[] = L"D:PAI(D;;FR;;;LG)(A;;FA;;;WD)"; constexpr wchar_t kTest1DaclNoInherit[] = L"D:P(A;;FR;;;AU)(A;;FA;;;WD)"; constexpr wchar_t kTest2DaclNoInherit[] = L"D:P(A;;FA;;;BA)(A;;FA;;;AU)(A;;FA;;;WD)"; constexpr wchar_t kBaseDirDacl[] = L"D:P(A;OICI;FA;;;WD)"; constexpr wchar_t kTest1InheritedDacl[] = L"D:(A;ID;FA;;;WD)"; constexpr wchar_t kBaseDir2Dacl[] = L"D:PAI(A;OICI;FR;;;AU)(A;OICI;FA;;;WD)"; constexpr wchar_t kTest2InheritedDacl[] = L"D:AI(A;ID;FR;;;AU)(A;ID;FA;;;WD)"; constexpr wchar_t kBaseDir2DaclNoInherit[] = L"D:P(A;OICI;FR;;;AU)(A;OICI;FA;;;WD)"; constexpr wchar_t kTest2InheritedDaclNoInherit[] = L"D:P(A;;FA;;;WD)"; constexpr wchar_t kTest3InheritedDacl[] = L"D:(A;ID;FR;;;AU)(A;ID;FA;;;WD)"; constexpr wchar_t kNoWriteDacDacl[] = L"D:(D;;WD;;;OW)(A;;FRSD;;;WD)"; constexpr wchar_t kAuthenticatedUsersSid[] = L"AU"; constexpr wchar_t kLocalGuestSid[] = L"LG"; std::wstring GetFileDacl(const FilePath& path) { PSECURITY_DESCRIPTOR sd; if (::GetNamedSecurityInfo(path.value().c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, nullptr, nullptr, &sd) != ERROR_SUCCESS) { return std::wstring(); } auto sd_ptr = TakeLocalAlloc(sd); LPWSTR sddl; if (!::ConvertSecurityDescriptorToStringSecurityDescriptor( sd_ptr.get(), SDDL_REVISION_1, DACL_SECURITY_INFORMATION, &sddl, nullptr)) { return std::wstring(); } return TakeLocalAlloc(sddl).get(); } bool CreateWithDacl(const FilePath& path, const wchar_t* sddl, bool directory) { PSECURITY_DESCRIPTOR sd; if (!::ConvertStringSecurityDescriptorToSecurityDescriptor( sddl, SDDL_REVISION_1, &sd, nullptr)) { return false; } auto sd_ptr = TakeLocalAlloc(sd); SECURITY_ATTRIBUTES security_attr = {}; security_attr.nLength = sizeof(security_attr); security_attr.lpSecurityDescriptor = sd_ptr.get(); if (directory) return !!::CreateDirectory(path.value().c_str(), &security_attr); return ScopedHandle(::CreateFile(path.value().c_str(), GENERIC_ALL, 0, &security_attr, CREATE_ALWAYS, 0, nullptr)) .is_valid(); } } // namespace TEST(SecurityUtilTest, GrantAccessToPathErrorCase) { ScopedTempDir temp_dir; auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid}); ASSERT_TRUE(sids); ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"test"); EXPECT_FALSE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_FALSE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, false)); ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false)); EXPECT_TRUE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_TRUE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, false)); std::vector large_sid_list; while (large_sid_list.size() < 0x10000) { auto sid = Sid::FromSddlString( base::StringPrintf(L"S-1-5-1234-%zu", large_sid_list.size()).c_str()); ASSERT_TRUE(sid); large_sid_list.emplace_back(std::move(*sid)); } EXPECT_FALSE(GrantAccessToPath(path, large_sid_list, FILE_GENERIC_READ, NO_INHERITANCE, false)); path = temp_dir.GetPath().Append(L"test_nowritedac"); ASSERT_TRUE(CreateWithDacl(path, kNoWriteDacDacl, false)); EXPECT_FALSE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_FALSE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, false)); } TEST(SecurityUtilTest, GrantAccessToPathFile) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"test"); ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid}); ASSERT_TRUE(sids); EXPECT_TRUE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_EQ(kTest1Dacl, GetFileDacl(path)); auto sids2 = Sid::FromSddlStringVector({L"S-1-5-11", L"BA"}); ASSERT_TRUE(sids2); EXPECT_TRUE( GrantAccessToPath(path, *sids2, GENERIC_ALL, NO_INHERITANCE, true)); EXPECT_EQ(kTest2Dacl, GetFileDacl(path)); } TEST(SecurityUtilTest, GrantAccessToPathFileNoInherit) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"test"); ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); EXPECT_TRUE( GrantAccessToPath(path, {}, FILE_GENERIC_READ, NO_INHERITANCE, false)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid}); ASSERT_TRUE(sids); EXPECT_TRUE( GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, false)); EXPECT_EQ(kTest1DaclNoInherit, GetFileDacl(path)); auto sids2 = Sid::FromSddlStringVector({L"S-1-5-11", L"BA"}); ASSERT_TRUE(sids2); EXPECT_TRUE( GrantAccessToPath(path, *sids2, GENERIC_ALL, NO_INHERITANCE, false)); EXPECT_EQ(kTest2DaclNoInherit, GetFileDacl(path)); } TEST(SecurityUtilTest, DenyAccessToPathFile) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"test"); ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); EXPECT_TRUE( DenyAccessToPath(path, {}, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); auto sids = Sid::FromSddlStringVector({kLocalGuestSid}); ASSERT_TRUE(sids); EXPECT_TRUE( DenyAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_EQ(kTest1DenyDacl, GetFileDacl(path)); } TEST(SecurityUtilTest, DenyAccessToPathFileMultiple) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"test"); ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false)); EXPECT_EQ(kBaseDacl, GetFileDacl(path)); auto sids = Sid::FromSddlStringVector({kLocalGuestSid}); ASSERT_TRUE(sids); EXPECT_TRUE( DenyAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); // Verify setting same ACE on same file does not change the ACL. EXPECT_TRUE( DenyAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_TRUE( DenyAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true)); EXPECT_EQ(kTest1DenyDacl, GetFileDacl(path)); } TEST(SecurityUtilTest, GrantAccessToPathDirectory) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"testdir"); ASSERT_TRUE(CreateWithDacl(path, kBaseDirDacl, true)); EXPECT_EQ(kBaseDirDacl, GetFileDacl(path)); FilePath file_path = path.Append(L"test"); File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE); ASSERT_TRUE(file.IsValid()); file.Close(); EXPECT_EQ(kTest1InheritedDacl, GetFileDacl(file_path)); auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid}); ASSERT_TRUE(sids); EXPECT_TRUE(GrantAccessToPath(path, *sids, FILE_GENERIC_READ, OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, true)); EXPECT_EQ(kBaseDir2Dacl, GetFileDacl(path)); EXPECT_EQ(kTest2InheritedDacl, GetFileDacl(file_path)); } TEST(SecurityUtilTest, GrantAccessToPathDirectoryNoInherit) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath path = temp_dir.GetPath().Append(L"testdir"); ASSERT_TRUE(CreateWithDacl(path, kBaseDirDacl, true)); EXPECT_EQ(kBaseDirDacl, GetFileDacl(path)); FilePath file_path = path.Append(L"test"); File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE); ASSERT_TRUE(file.IsValid()); file.Close(); EXPECT_EQ(kTest1InheritedDacl, GetFileDacl(file_path)); auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid}); ASSERT_TRUE(sids); EXPECT_TRUE(GrantAccessToPath(path, *sids, FILE_GENERIC_READ, OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, false)); EXPECT_EQ(kBaseDir2DaclNoInherit, GetFileDacl(path)); EXPECT_EQ(kTest2InheritedDaclNoInherit, GetFileDacl(file_path)); FilePath file_path2 = path.Append(L"test2"); File file2(file_path2, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE); ASSERT_TRUE(file2.IsValid()); file2.Close(); EXPECT_EQ(kTest3InheritedDacl, GetFileDacl(file_path2)); } TEST(SecurityUtilTest, CloneSidVector) { std::vector sids = Sid::FromKnownSidVector({WellKnownSid::kNull, WellKnownSid::kWorld}); std::vector clone = CloneSidVector(sids); ASSERT_EQ(sids.size(), clone.size()); for (size_t index = 0; index < sids.size(); ++index) { ASSERT_EQ(sids[index], clone[index]); ASSERT_NE(sids[index].GetPSID(), clone[index].GetPSID()); } ASSERT_EQ(CloneSidVector(std::vector()).size(), 0U); } TEST(SecurityUtilTest, AppendSidVector) { std::vector sids = Sid::FromKnownSidVector({WellKnownSid::kNull, WellKnownSid::kWorld}); std::vector total_sids; AppendSidVector(total_sids, sids); EXPECT_EQ(total_sids.size(), sids.size()); std::vector sids2 = Sid::FromKnownSidVector( {WellKnownSid::kCreatorOwner, WellKnownSid::kNetwork}); AppendSidVector(total_sids, sids2); EXPECT_EQ(total_sids.size(), sids.size() + sids2.size()); auto sid_interator = total_sids.cbegin(); for (size_t index = 0; index < sids.size(); ++index) { ASSERT_EQ(*sid_interator, sids[index]); ASSERT_NE(sid_interator->GetPSID(), sids[index].GetPSID()); sid_interator++; } for (size_t index = 0; index < sids2.size(); ++index) { ASSERT_EQ(*sid_interator, sids2[index]); ASSERT_NE(sid_interator->GetPSID(), sids2[index].GetPSID()); sid_interator++; } } TEST(SecurityUtilTest, GetGrantedAccess) { EXPECT_FALSE(GetGrantedAccess(nullptr)); ScopedHandle handle(::CreateMutexEx(nullptr, nullptr, 0, MUTEX_MODIFY_STATE)); EXPECT_EQ(GetGrantedAccess(handle.get()), DWORD{MUTEX_MODIFY_STATE}); handle.Set(::CreateMutexEx(nullptr, nullptr, 0, READ_CONTROL)); EXPECT_EQ(GetGrantedAccess(handle.get()), DWORD{READ_CONTROL}); handle.Set(::CreateMutexEx(nullptr, nullptr, 0, GENERIC_ALL)); EXPECT_EQ(GetGrantedAccess(handle.get()), DWORD{MUTEX_ALL_ACCESS}); } } // namespace win } // namespace base