chromium/base/win/sid_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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

// This file contains unit tests for the sid class.

#include "base/win/sid.h"

#include <windows.h>

#include <sddl.h>

#include <optional>

#include "base/ranges/algorithm.h"
#include "base/win/atl.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_localalloc.h"
#include "base/win/win_util.h"
#include "build/branding_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::win {

namespace {

bool EqualSid(const std::optional<Sid>& sid, const ATL::CSid& compare_sid) {
  if (!sid)
    return false;
  return sid->Equal(const_cast<SID*>(compare_sid.GetPSID()));
}

bool EqualSid(const Sid& sid, const std::wstring& sddl_sid) {
  PSID compare_sid;
  if (!::ConvertStringSidToSid(sddl_sid.c_str(), &compare_sid)) {
    return false;
  }
  auto sid_ptr = TakeLocalAlloc(compare_sid);
  return sid.Equal(sid_ptr.get());
}

bool EqualSid(const std::optional<Sid>& sid, WELL_KNOWN_SID_TYPE known_sid) {
  if (!sid)
    return false;
  char known_sid_buffer[SECURITY_MAX_SID_SIZE] = {};
  DWORD size = SECURITY_MAX_SID_SIZE;
  if (!::CreateWellKnownSid(known_sid, nullptr, known_sid_buffer, &size))
    return false;

  return sid->Equal(known_sid_buffer);
}

bool TestSidVector(std::optional<std::vector<Sid>> sids,
                   const std::vector<std::wstring>& sddl) {
  return sids && ranges::equal(*sids, sddl,
                               [](const Sid& sid, const std::wstring& sddl) {
                                 return EqualSid(sid, sddl);
                               });
}

bool TestFromSddlStringVector(const std::vector<std::wstring> sddl) {
  return TestSidVector(Sid::FromSddlStringVector(sddl), sddl);
}

typedef decltype(::DeriveCapabilitySidsFromName)*
    DeriveCapabilitySidsFromNameFunc;

// Get the DeriveCapabilitySidsFromName API dynamically. Versions of Windows 10
// older than 1809 do not implement this method. By loading dynamically we can
// skip tests when running on these older versions. Online documentation for
// this API claims it's supported back to Windows 2003, however this is entirely
// incorrect.
DeriveCapabilitySidsFromNameFunc GetDeriveCapabilitySidsFromName() {
  static const DeriveCapabilitySidsFromNameFunc derive_capability_sids =
      []() -> DeriveCapabilitySidsFromNameFunc {
    HMODULE module = GetModuleHandle(L"api-ms-win-security-base-l1-2-2.dll");
    if (!module) {
      return nullptr;
    }
    return reinterpret_cast<DeriveCapabilitySidsFromNameFunc>(
        ::GetProcAddress(module, "DeriveCapabilitySidsFromName"));
  }();

  return derive_capability_sids;
}

bool EqualNamedCapSid(const Sid& sid, const std::wstring& capability_name) {
  DeriveCapabilitySidsFromNameFunc derive_capability_sids =
      GetDeriveCapabilitySidsFromName();
  CHECK(derive_capability_sids);

  // Pre-reserve some space for SID deleters.
  std::vector<base::win::ScopedLocalAlloc> deleter_list;
  deleter_list.reserve(16);

  PSID* capability_groups = nullptr;
  DWORD capability_group_count = 0;
  PSID* capability_sids = nullptr;
  DWORD capability_sid_count = 0;

  CHECK(derive_capability_sids(capability_name.c_str(), &capability_groups,
                               &capability_group_count, &capability_sids,
                               &capability_sid_count));
  deleter_list.emplace_back(capability_groups);
  deleter_list.emplace_back(capability_sids);

  for (DWORD i = 0; i < capability_group_count; ++i) {
    deleter_list.emplace_back(capability_groups[i]);
  }
  for (DWORD i = 0; i < capability_sid_count; ++i) {
    deleter_list.emplace_back(capability_sids[i]);
  }

  CHECK_GE(capability_sid_count, 1U);
  return sid.Equal(capability_sids[0]);
}

struct KnownCapabilityTestEntry {
  WellKnownCapability capability;
  const wchar_t* sddl_sid;
};

struct KnownSidTestEntry {
  WellKnownSid sid;
  WELL_KNOWN_SID_TYPE well_known_sid;
};

}  // namespace

// Tests the creation of a Sid.
TEST(SidTest, Initializers) {
  ATL::CSid sid_world = ATL::Sids::World();
  PSID sid_world_pointer = const_cast<SID*>(sid_world.GetPSID());

  // Check the PSID constructor.
  std::optional<Sid> sid_sid_star = Sid::FromPSID(sid_world_pointer);
  ASSERT_TRUE(EqualSid(sid_sid_star, sid_world));

  char invalid_sid[16] = {};
  ASSERT_FALSE(Sid::FromPSID(invalid_sid));

  std::optional<Sid> sid_sddl = Sid::FromSddlString(L"S-1-1-0");
  ASSERT_TRUE(sid_sddl);
  ASSERT_TRUE(EqualSid(sid_sddl, sid_world));
}

TEST(SidTest, KnownCapability) {
  const KnownCapabilityTestEntry capabilities[] = {
      {WellKnownCapability::kInternetClient, L"S-1-15-3-1"},
      {WellKnownCapability::kInternetClientServer, L"S-1-15-3-2"},
      {WellKnownCapability::kPrivateNetworkClientServer, L"S-1-15-3-3"},
      {WellKnownCapability::kPicturesLibrary, L"S-1-15-3-4"},
      {WellKnownCapability::kVideosLibrary, L"S-1-15-3-5"},
      {WellKnownCapability::kMusicLibrary, L"S-1-15-3-6"},
      {WellKnownCapability::kDocumentsLibrary, L"S-1-15-3-7"},
      {WellKnownCapability::kEnterpriseAuthentication, L"S-1-15-3-8"},
      {WellKnownCapability::kSharedUserCertificates, L"S-1-15-3-9"},
      {WellKnownCapability::kRemovableStorage, L"S-1-15-3-10"},
      {WellKnownCapability::kAppointments, L"S-1-15-3-11"},
      {WellKnownCapability::kContacts, L"S-1-15-3-12"},
  };

  for (auto capability : capabilities) {
    EXPECT_TRUE(EqualSid(Sid::FromKnownCapability(capability.capability),
                         capability.sddl_sid))
        << "Known Capability: " << capability.sddl_sid;
    EXPECT_TRUE(EqualSid(Sid(capability.capability), capability.sddl_sid))
        << "Known Capability: " << capability.sddl_sid;
  }
}

TEST(SidTest, NamedCapability) {
  if (!GetDeriveCapabilitySidsFromName()) {
    GTEST_SKIP()
        << "Platform doesn't support DeriveCapabilitySidsFromName function.";
  }
  const std::wstring capabilities[] = {L"",
                                       L"InternetClient",
                                       L"InternetClientServer",
                                       L"PrivateNetworkClientServer",
                                       L"PicturesLibrary",
                                       L"VideosLibrary",
                                       L"MusicLibrary",
                                       L"DocumentsLibrary",
                                       L"EnterpriseAuthentication",
                                       L"SharedUserCertificates",
                                       L"RemovableStorage",
                                       L"Appointments",
                                       L"Contacts",
                                       L"registryRead",
                                       L"lpacCryptoServices"};

  for (const std::wstring& capability : capabilities) {
    EXPECT_TRUE(
        EqualNamedCapSid(Sid::FromNamedCapability(capability), capability))
        << "Named Capability: " << capability;
  }
}

TEST(SidTest, KnownSids) {
  const KnownSidTestEntry known_sids[] = {
      {WellKnownSid::kNull, ::WinNullSid},
      {WellKnownSid::kWorld, ::WinWorldSid},
      {WellKnownSid::kCreatorOwner, ::WinCreatorOwnerSid},
      {WellKnownSid::kNetwork, ::WinNetworkSid},
      {WellKnownSid::kBatch, ::WinBatchSid},
      {WellKnownSid::kInteractive, ::WinInteractiveSid},
      {WellKnownSid::kService, ::WinServiceSid},
      {WellKnownSid::kAnonymous, ::WinAnonymousSid},
      {WellKnownSid::kSelf, ::WinSelfSid},
      {WellKnownSid::kAuthenticatedUser, ::WinAuthenticatedUserSid},
      {WellKnownSid::kRestricted, ::WinRestrictedCodeSid},
      {WellKnownSid::kLocalSystem, ::WinLocalSystemSid},
      {WellKnownSid::kLocalService, ::WinLocalServiceSid},
      {WellKnownSid::kNetworkService, ::WinNetworkServiceSid},
      {WellKnownSid::kBuiltinAdministrators, ::WinBuiltinAdministratorsSid},
      {WellKnownSid::kBuiltinUsers, ::WinBuiltinUsersSid},
      {WellKnownSid::kBuiltinGuests, ::WinBuiltinGuestsSid},
      {WellKnownSid::kUntrustedLabel, ::WinUntrustedLabelSid},
      {WellKnownSid::kLowLabel, ::WinLowLabelSid},
      {WellKnownSid::kMediumLabel, ::WinMediumLabelSid},
      {WellKnownSid::kHighLabel, ::WinHighLabelSid},
      {WellKnownSid::kSystemLabel, ::WinSystemLabelSid},
      {WellKnownSid::kWriteRestricted, ::WinWriteRestrictedCodeSid},
      {WellKnownSid::kCreatorOwnerRights, ::WinCreatorOwnerRightsSid},
      {WellKnownSid::kAllApplicationPackages, ::WinBuiltinAnyPackageSid}};

  for (auto known_sid : known_sids) {
    EXPECT_TRUE(
        EqualSid(Sid::FromKnownSid(known_sid.sid), known_sid.well_known_sid))
        << "Known Sid: " << static_cast<int>(known_sid.sid);
    EXPECT_TRUE(EqualSid(Sid(known_sid.sid), known_sid.well_known_sid))
        << "Known Sid: " << static_cast<int>(known_sid.sid);
  }

  EXPECT_TRUE(EqualSid(
      Sid::FromKnownSid(WellKnownSid::kAllRestrictedApplicationPackages),
      L"S-1-15-2-2"));
}

TEST(SidTest, SddlString) {
  std::optional<Sid> sid_sddl = Sid::FromSddlString(L"S-1-1-0");
  ASSERT_TRUE(sid_sddl);
  std::optional<std::wstring> sddl_str = sid_sddl->ToSddlString();
  ASSERT_TRUE(sddl_str);
  ASSERT_EQ(L"S-1-1-0", *sddl_str);
  ASSERT_FALSE(Sid::FromSddlString(L"X-1-1-0"));
  ASSERT_FALSE(Sid::FromSddlString(L""));
}

TEST(SidTest, RandomSid) {
  Sid sid1 = Sid::GenerateRandomSid();
  Sid sid2 = Sid::GenerateRandomSid();
  EXPECT_NE(sid1, sid2);
}

TEST(SidTest, FromIntegrityLevel) {
  ASSERT_TRUE(EqualSid(
      Sid::FromIntegrityLevel(SECURITY_MANDATORY_UNTRUSTED_RID), L"S-1-16-0"));
  ASSERT_TRUE(EqualSid(Sid::FromIntegrityLevel(SECURITY_MANDATORY_LOW_RID),
                       L"S-1-16-4096"));
  ASSERT_TRUE(EqualSid(Sid::FromIntegrityLevel(SECURITY_MANDATORY_MEDIUM_RID),
                       L"S-1-16-8192"));
  ASSERT_TRUE(
      EqualSid(Sid::FromIntegrityLevel(SECURITY_MANDATORY_MEDIUM_PLUS_RID),
               L"S-1-16-8448"));
  ASSERT_TRUE(EqualSid(Sid::FromIntegrityLevel(SECURITY_MANDATORY_HIGH_RID),
                       L"S-1-16-12288"));
  ASSERT_TRUE(EqualSid(Sid::FromIntegrityLevel(SECURITY_MANDATORY_SYSTEM_RID),
                       L"S-1-16-16384"));
  ASSERT_TRUE(EqualSid(Sid::FromIntegrityLevel(1234), L"S-1-16-1234"));
}

TEST(SidTest, FromSddlStringVector) {
  ASSERT_TRUE(
      TestFromSddlStringVector({L"S-1-1-0", L"S-1-15-2-2", L"S-1-15-3-2"}));
  ASSERT_FALSE(
      TestFromSddlStringVector({L"S-1-1-0", L"X-1-15-2-2", L"S-1-15-3-2"}));
  ASSERT_FALSE(TestFromSddlStringVector({L""}));
  ASSERT_TRUE(TestFromSddlStringVector({}));
}

TEST(SidTest, FromNamedCapabilityVector) {
  if (!GetDeriveCapabilitySidsFromName()) {
    GTEST_SKIP()
        << "Platform doesn't support DeriveCapabilitySidsFromName function.";
  }
  std::vector<std::wstring> capabilities = {L"",
                                            L"InternetClient",
                                            L"InternetClientServer",
                                            L"PrivateNetworkClientServer",
                                            L"PicturesLibrary",
                                            L"VideosLibrary",
                                            L"MusicLibrary",
                                            L"DocumentsLibrary",
                                            L"EnterpriseAuthentication",
                                            L"SharedUserCertificates",
                                            L"RemovableStorage",
                                            L"Appointments",
                                            L"Contacts",
                                            L"registryRead",
                                            L"lpacCryptoServices"};

  ASSERT_TRUE(ranges::equal(Sid::FromNamedCapabilityVector(capabilities),
                            capabilities, EqualNamedCapSid));
  EXPECT_EQ(Sid::FromNamedCapabilityVector({}).size(), 0U);
}

TEST(SidTest, FromKnownCapabilityVector) {
  ASSERT_TRUE(TestSidVector(
      Sid::FromKnownCapabilityVector(
          {WellKnownCapability::kInternetClient,
           WellKnownCapability::kInternetClientServer,
           WellKnownCapability::kPrivateNetworkClientServer,
           WellKnownCapability::kPicturesLibrary,
           WellKnownCapability::kVideosLibrary,
           WellKnownCapability::kMusicLibrary,
           WellKnownCapability::kDocumentsLibrary,
           WellKnownCapability::kEnterpriseAuthentication,
           WellKnownCapability::kSharedUserCertificates,
           WellKnownCapability::kRemovableStorage,
           WellKnownCapability::kAppointments, WellKnownCapability::kContacts}),
      {L"S-1-15-3-1", L"S-1-15-3-2", L"S-1-15-3-3", L"S-1-15-3-4",
       L"S-1-15-3-5", L"S-1-15-3-6", L"S-1-15-3-7", L"S-1-15-3-8",
       L"S-1-15-3-9", L"S-1-15-3-10", L"S-1-15-3-11", L"S-1-15-3-12"}));

  ASSERT_FALSE(TestSidVector(
      Sid::FromKnownCapabilityVector({WellKnownCapability::kInternetClient}),
      {L"S-1-1-0"}));
}

TEST(SidTest, FromKnownSidVector) {
  ASSERT_TRUE(TestSidVector(
      Sid::FromKnownSidVector({WellKnownSid::kNull, WellKnownSid::kWorld}),
      {L"S-1-0-0", L"S-1-1-0"}));

  ASSERT_FALSE(TestSidVector(Sid::FromKnownSidVector({WellKnownSid::kNull}),
                             {L"S-1-1-0"}));
}

TEST(SidTest, Equal) {
  Sid world_sid = Sid::FromKnownSid(WellKnownSid::kWorld);
  EXPECT_EQ(world_sid, world_sid);
  auto world_sid_sddl = Sid::FromSddlString(L"S-1-1-0");
  ASSERT_TRUE(world_sid_sddl);
  EXPECT_EQ(world_sid, world_sid_sddl);
  EXPECT_EQ(world_sid_sddl, world_sid);
  EXPECT_TRUE(world_sid.Equal(world_sid_sddl->GetPSID()));
  EXPECT_TRUE(world_sid_sddl->Equal(world_sid.GetPSID()));
  Sid null_sid = Sid::FromKnownSid(WellKnownSid::kNull);
  EXPECT_NE(world_sid, null_sid);
  EXPECT_NE(null_sid, world_sid);
  EXPECT_FALSE(world_sid.Equal(null_sid.GetPSID()));
  EXPECT_FALSE(null_sid.Equal(world_sid.GetPSID()));
}

TEST(SidTest, Clone) {
  Sid world_sid = Sid::FromKnownSid(WellKnownSid::kWorld);
  auto world_sid_clone = world_sid.Clone();
  EXPECT_NE(world_sid.GetPSID(), world_sid_clone.GetPSID());
  EXPECT_EQ(world_sid, world_sid_clone);
}

}  // namespace base::win