chromium/base/win/security_util_unittest.cc

// 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"

// clang-format off
#include <windows.h>  // Must be in front of other Windows header files.
// clang-format on

#include <aclapi.h>
#include <sddl.h>

#include <utility>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/test/test_file_util.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";

}  // 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<Sid> large_sid_list;
  while (large_sid_list.size() < 0x10000) {
    auto sid = Sid::FromSddlString(L"S-1-5-1234-" +
                                   NumberToWString(large_sid_list.size()));
    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<Sid> sids =
      Sid::FromKnownSidVector({WellKnownSid::kNull, WellKnownSid::kWorld});
  std::vector<Sid> 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<Sid>()).size(), 0U);
}

TEST(SecurityUtilTest, AppendSidVector) {
  std::vector<Sid> sids =
      Sid::FromKnownSidVector({WellKnownSid::kNull, WellKnownSid::kWorld});

  std::vector<Sid> total_sids;
  AppendSidVector(total_sids, sids);
  EXPECT_EQ(total_sids.size(), sids.size());

  std::vector<Sid> 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