// Copyright 2022 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_descriptor.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 <vector>

#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/win/scoped_localalloc.h"

namespace base::win {

namespace {
template <typename T>
std::optional<T> CloneValue(const std::optional<T>& value) {
  if (!value) {
    return std::nullopt;
  }
  return value->Clone();
}

PSID UnwrapSid(const std::optional<Sid>& sid) {
  if (!sid) {
    return nullptr;
  }
  return sid->GetPSID();
}

PACL UnwrapAcl(std::optional<AccessControlList>& acl) {
  if (!acl) {
    return nullptr;
  }
  return acl->get();
}

SE_OBJECT_TYPE ConvertObjectType(SecurityObjectType object_type) {
  switch (object_type) {
    case SecurityObjectType::kFile:
      return SE_FILE_OBJECT;
    case SecurityObjectType::kRegistry:
      return SE_REGISTRY_KEY;
    case SecurityObjectType::kWindowStation:
    case SecurityObjectType::kDesktop:
      return SE_WINDOW_OBJECT;
    case SecurityObjectType::kKernel:
      return SE_KERNEL_OBJECT;
  }
  return SE_UNKNOWN_OBJECT_TYPE;
}

GENERIC_MAPPING GetGenericMappingForType(SecurityObjectType object_type) {
  GENERIC_MAPPING generic_mapping = {};
  switch (object_type) {
    case SecurityObjectType::kFile:
      generic_mapping.GenericRead = FILE_GENERIC_READ;
      generic_mapping.GenericWrite = FILE_GENERIC_WRITE;
      generic_mapping.GenericExecute = FILE_GENERIC_EXECUTE;
      generic_mapping.GenericAll = FILE_ALL_ACCESS;
      break;
    case SecurityObjectType::kRegistry:
      generic_mapping.GenericRead = KEY_READ;
      generic_mapping.GenericWrite = KEY_WRITE;
      generic_mapping.GenericExecute = KEY_EXECUTE;
      generic_mapping.GenericAll = KEY_ALL_ACCESS;
      break;
    case SecurityObjectType::kDesktop:
      generic_mapping.GenericRead =
          STANDARD_RIGHTS_READ | DESKTOP_READOBJECTS | DESKTOP_ENUMERATE;
      generic_mapping.GenericWrite =
          STANDARD_RIGHTS_WRITE | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU |
          DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD |
          DESKTOP_JOURNALPLAYBACK | DESKTOP_WRITEOBJECTS;
      generic_mapping.GenericExecute =
          STANDARD_RIGHTS_EXECUTE | DESKTOP_SWITCHDESKTOP;
      generic_mapping.GenericAll =
          STANDARD_RIGHTS_REQUIRED | DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
          DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK |
          DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP |
          DESKTOP_WRITEOBJECTS;
      break;
    case SecurityObjectType::kWindowStation:
      generic_mapping.GenericRead = STANDARD_RIGHTS_READ | WINSTA_ENUMDESKTOPS |
                                    WINSTA_ENUMERATE | WINSTA_READATTRIBUTES |
                                    WINSTA_READSCREEN;
      generic_mapping.GenericWrite =
          STANDARD_RIGHTS_WRITE | WINSTA_ACCESSCLIPBOARD |
          WINSTA_CREATEDESKTOP | WINSTA_WRITEATTRIBUTES;
      generic_mapping.GenericExecute = STANDARD_RIGHTS_EXECUTE |
                                       WINSTA_ACCESSGLOBALATOMS |
                                       WINSTA_EXITWINDOWS;
      generic_mapping.GenericAll =
          STANDARD_RIGHTS_REQUIRED | WINSTA_ACCESSCLIPBOARD |
          WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP |
          WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS |
          WINSTA_READATTRIBUTES | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES;
      break;
    case SecurityObjectType::kKernel:
      NOTREACHED();
  }
  return generic_mapping;
}

template <typename T>
std::optional<SecurityDescriptor> GetSecurityDescriptor(
    T object,
    SecurityObjectType object_type,
    SECURITY_INFORMATION security_info,
    DWORD(WINAPI* get_sd)(T,
                          SE_OBJECT_TYPE,
                          SECURITY_INFORMATION,
                          PSID*,
                          PSID*,
                          PACL*,
                          PACL*,
                          PSECURITY_DESCRIPTOR*)) {
  PSECURITY_DESCRIPTOR sd = nullptr;

  DWORD error = get_sd(object, ConvertObjectType(object_type), security_info,
                       nullptr, nullptr, nullptr, nullptr, &sd);
  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed getting security descriptor for object.";
    return std::nullopt;
  }
  auto sd_ptr = TakeLocalAlloc(sd);
  return SecurityDescriptor::FromPointer(sd_ptr.get());
}

template <typename T>
bool SetSecurityDescriptor(const SecurityDescriptor& sd,
                           T object,
                           SecurityObjectType object_type,
                           SECURITY_INFORMATION security_info,
                           DWORD(WINAPI* set_sd)(T,
                                                 SE_OBJECT_TYPE,
                                                 SECURITY_INFORMATION,
                                                 PSID,
                                                 PSID,
                                                 PACL,
                                                 PACL)) {
  auto security_descriptor = sd.Clone();

  security_info &= ~(PROTECTED_DACL_SECURITY_INFORMATION |
                     UNPROTECTED_DACL_SECURITY_INFORMATION |
                     PROTECTED_SACL_SECURITY_INFORMATION |
                     UNPROTECTED_SACL_SECURITY_INFORMATION);
  if (security_info & DACL_SECURITY_INFORMATION) {
    if (security_descriptor.dacl_protected()) {
      security_info |= PROTECTED_DACL_SECURITY_INFORMATION;
    } else {
      security_info |= UNPROTECTED_DACL_SECURITY_INFORMATION;
    }
  }
  if (security_info & SACL_SECURITY_INFORMATION) {
    if (security_descriptor.sacl_protected()) {
      security_info |= PROTECTED_SACL_SECURITY_INFORMATION;
    } else {
      security_info |= UNPROTECTED_SACL_SECURITY_INFORMATION;
    }
  }
  DWORD error = set_sd(object, ConvertObjectType(object_type), security_info,
                       UnwrapSid(security_descriptor.owner()),
                       UnwrapSid(security_descriptor.group()),
                       UnwrapAcl(security_descriptor.dacl()),
                       UnwrapAcl(security_descriptor.sacl()));
  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed setting DACL for object.";
    return false;
  }
  return true;
}

std::optional<Sid> GetSecurityDescriptorSid(
    PSECURITY_DESCRIPTOR sd,
    BOOL(WINAPI* get_sid)(PSECURITY_DESCRIPTOR, PSID*, LPBOOL)) {
  PSID sid;
  BOOL defaulted;
  if (!get_sid(sd, &sid, &defaulted) || !sid) {
    return std::nullopt;
  }
  return Sid::FromPSID(sid);
}

std::optional<AccessControlList> GetSecurityDescriptorAcl(
    PSECURITY_DESCRIPTOR sd,
    BOOL(WINAPI* get_acl)(PSECURITY_DESCRIPTOR, LPBOOL, PACL*, LPBOOL)) {
  PACL acl;
  BOOL present;
  BOOL defaulted;
  if (!get_acl(sd, &present, &acl, &defaulted) || !present) {
    return std::nullopt;
  }
  return AccessControlList::FromPACL(acl);
}

}  // namespace

SecurityDescriptor::SelfRelative::SelfRelative(const SelfRelative&) = default;
SecurityDescriptor::SelfRelative::~SelfRelative() = default;
SecurityDescriptor::SelfRelative::SelfRelative(std::vector<uint8_t>&& sd)
    : sd_(sd) {}

std::optional<SecurityDescriptor> SecurityDescriptor::FromPointer(
    PSECURITY_DESCRIPTOR sd) {
  if (!sd || !::IsValidSecurityDescriptor(sd)) {
    ::SetLastError(ERROR_INVALID_SECURITY_DESCR);
    return std::nullopt;
  }

  SECURITY_DESCRIPTOR_CONTROL control;
  DWORD revision;
  if (!::GetSecurityDescriptorControl(sd, &control, &revision)) {
    return std::nullopt;
  }

  return SecurityDescriptor{
      GetSecurityDescriptorSid(sd, ::GetSecurityDescriptorOwner),
      GetSecurityDescriptorSid(sd, ::GetSecurityDescriptorGroup),
      GetSecurityDescriptorAcl(sd, ::GetSecurityDescriptorDacl),
      !!(control & SE_DACL_PROTECTED),
      GetSecurityDescriptorAcl(sd, ::GetSecurityDescriptorSacl),
      !!(control & SE_SACL_PROTECTED)};
}

std::optional<SecurityDescriptor> SecurityDescriptor::FromFile(
    const base::FilePath& path,
    SECURITY_INFORMATION security_info) {
  return FromName(path.value(), SecurityObjectType::kFile, security_info);
}

std::optional<SecurityDescriptor> SecurityDescriptor::FromName(
    const std::wstring& name,
    SecurityObjectType object_type,
    SECURITY_INFORMATION security_info) {
  return GetSecurityDescriptor(name.c_str(), object_type, security_info,
                               ::GetNamedSecurityInfo);
}

std::optional<SecurityDescriptor> SecurityDescriptor::FromHandle(
    HANDLE handle,
    SecurityObjectType object_type,
    SECURITY_INFORMATION security_info) {
  return GetSecurityDescriptor<HANDLE>(handle, object_type, security_info,
                                       ::GetSecurityInfo);
}

std::optional<SecurityDescriptor> SecurityDescriptor::FromSddl(
    const std::wstring& sddl) {
  PSECURITY_DESCRIPTOR sd;
  if (!::ConvertStringSecurityDescriptorToSecurityDescriptor(
          sddl.c_str(), SDDL_REVISION_1, &sd, nullptr)) {
    return std::nullopt;
  }
  auto sd_ptr = TakeLocalAlloc(sd);
  return FromPointer(sd_ptr.get());
}

SecurityDescriptor::SecurityDescriptor() = default;
SecurityDescriptor::SecurityDescriptor(SecurityDescriptor&&) = default;
SecurityDescriptor& SecurityDescriptor::operator=(SecurityDescriptor&&) =
    default;
SecurityDescriptor::~SecurityDescriptor() = default;

bool SecurityDescriptor::WriteToFile(const base::FilePath& path,
                                     SECURITY_INFORMATION security_info) const {
  return WriteToName(path.value(), SecurityObjectType::kFile, security_info);
}

bool SecurityDescriptor::WriteToName(const std::wstring& name,
                                     SecurityObjectType object_type,
                                     SECURITY_INFORMATION security_info) const {
  return SetSecurityDescriptor<wchar_t*>(
      *this, const_cast<wchar_t*>(name.c_str()), object_type, security_info,
      ::SetNamedSecurityInfo);
}

bool SecurityDescriptor::WriteToHandle(
    HANDLE handle,
    SecurityObjectType object_type,
    SECURITY_INFORMATION security_info) const {
  return SetSecurityDescriptor<HANDLE>(*this, handle, object_type,
                                       security_info, ::SetSecurityInfo);
}

std::optional<std::wstring> SecurityDescriptor::ToSddl(
    SECURITY_INFORMATION security_info) const {
  // `ToAbsolute()` is not const-qualified as it returns non-const pointers by
  // populating the `SECURITY_DESCRIPTOR` with them. Since we're in a const-
  // qualified member method, we need to clone ourselves and call `ToAbsolute()`
  // on the clone.
  auto self = Clone();
  SECURITY_DESCRIPTOR sd = {};
  self.ToAbsolute(sd);

  LPWSTR sddl;
  if (!::ConvertSecurityDescriptorToStringSecurityDescriptor(
          &sd, SDDL_REVISION_1, security_info, &sddl, nullptr)) {
    return std::nullopt;
  }
  auto sddl_ptr = TakeLocalAlloc(sddl);
  return sddl_ptr.get();
}

void SecurityDescriptor::ToAbsolute(SECURITY_DESCRIPTOR& sd) {
  UNSAFE_TODO(memset(&sd, 0, sizeof(sd)));
  sd.Revision = SECURITY_DESCRIPTOR_REVISION;
  sd.Owner = owner_ ? owner_->GetPSID() : nullptr;
  sd.Group = group_ ? group_->GetPSID() : nullptr;
  if (dacl_) {
    sd.Dacl = dacl_->get();
    sd.Control |= SE_DACL_PRESENT;
    if (dacl_protected_) {
      sd.Control |= SE_DACL_PROTECTED;
    }
  }
  if (sacl_) {
    sd.Sacl = sacl_->get();
    sd.Control |= SE_SACL_PRESENT;
    if (sacl_protected_) {
      sd.Control |= SE_SACL_PROTECTED;
    }
  }
  DCHECK(::IsValidSecurityDescriptor(&sd));
}

std::optional<SecurityDescriptor::SelfRelative>
SecurityDescriptor::ToSelfRelative() const {
  // `ToAbsolute()` is not const-qualified as it returns non-const pointers by
  // populating the `SECURITY_DESCRIPTOR` with them. Since we're in a const-
  // qualified member method, we need to clone ourselves and call `ToAbsolute()`
  // on the clone.
  auto self = Clone();
  SECURITY_DESCRIPTOR sd = {};
  self.ToAbsolute(sd);

  DWORD size = sizeof(SECURITY_DESCRIPTOR_MIN_LENGTH);
  std::vector<uint8_t> buffer(SECURITY_DESCRIPTOR_MIN_LENGTH);
  if (::MakeSelfRelativeSD(&sd, buffer.data(), &size)) {
    return SelfRelative(std::move(buffer));
  }

  if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    return std::nullopt;
  }

  buffer.resize(size);
  if (!::MakeSelfRelativeSD(&sd, buffer.data(), &size)) {
    return std::nullopt;
  }
  return SelfRelative(std::move(buffer));
}

SecurityDescriptor SecurityDescriptor::Clone() const {
  return SecurityDescriptor{CloneValue(owner_), CloneValue(group_),
                            CloneValue(dacl_),  dacl_protected_,
                            CloneValue(sacl_),  sacl_protected_};
}

bool SecurityDescriptor::SetMandatoryLabel(DWORD integrity_level,
                                           DWORD inheritance,
                                           DWORD mandatory_policy) {
  std::optional<AccessControlList> sacl = AccessControlList::FromMandatoryLabel(
      integrity_level, inheritance, mandatory_policy);
  if (!sacl) {
    return false;
  }
  sacl_ = std::move(*sacl);
  return true;
}

bool SecurityDescriptor::SetDaclEntries(
    const std::vector<ExplicitAccessEntry>& entries) {
  if (!dacl_) {
    dacl_ = AccessControlList{};
  }
  return dacl_->SetEntries(entries);
}

bool SecurityDescriptor::SetDaclEntry(const Sid& sid,
                                      SecurityAccessMode mode,
                                      DWORD access_mask,
                                      DWORD inheritance) {
  if (!dacl_) {
    dacl_ = AccessControlList{};
  }
  return dacl_->SetEntry(sid, mode, access_mask, inheritance);
}

bool SecurityDescriptor::SetDaclEntry(WellKnownSid known_sid,
                                      SecurityAccessMode mode,
                                      DWORD access_mask,
                                      DWORD inheritance) {
  return SetDaclEntry(Sid(known_sid), mode, access_mask, inheritance);
}

std::optional<AccessCheckResult> SecurityDescriptor::AccessCheck(
    const AccessToken& token,
    ACCESS_MASK desired_access,
    const GENERIC_MAPPING& generic_mapping) {
  GENERIC_MAPPING local_mapping = generic_mapping;
  ::MapGenericMask(&desired_access, &local_mapping);

  // Allocate a privilege set which could cover all possible privileges.
  DWORD priv_set_length = checked_cast<DWORD>(
      sizeof(PRIVILEGE_SET) +
      (token.Privileges().size() * sizeof(LUID_AND_ATTRIBUTES)));
  std::vector<char> priv_set(priv_set_length);
  DWORD granted_access = 0;
  BOOL access_status = FALSE;
  SECURITY_DESCRIPTOR sd = {};
  ToAbsolute(sd);
  if (!::AccessCheck(&sd, token.get(), desired_access, &local_mapping,
                     reinterpret_cast<PPRIVILEGE_SET>(priv_set.data()),
                     &priv_set_length, &granted_access, &access_status)) {
    return std::nullopt;
  }
  return AccessCheckResult{granted_access, !!access_status};
}

std::optional<AccessCheckResult> SecurityDescriptor::AccessCheck(
    const AccessToken& token,
    ACCESS_MASK desired_access,
    SecurityObjectType object_type) {
  if (object_type == SecurityObjectType::kKernel) {
    ::SetLastError(ERROR_INVALID_PARAMETER);
    return std::nullopt;
  }
  return AccessCheck(token, desired_access,
                     GetGenericMappingForType(object_type));
}

SecurityDescriptor::SecurityDescriptor(std::optional<Sid>&& owner,
                                       std::optional<Sid>&& group,
                                       std::optional<AccessControlList>&& dacl,
                                       bool dacl_protected,
                                       std::optional<AccessControlList>&& sacl,
                                       bool sacl_protected) {
  owner_.swap(owner);
  group_.swap(group);
  dacl_.swap(dacl);
  dacl_protected_ = dacl_protected;
  sacl_.swap(sacl);
  sacl_protected_ = sacl_protected;
}

}  // namespace base::win
