// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <windows.h>

#include <Sddl.h>

#include <memory>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/win/windows_version.h"
#include "sandbox/win/src/app_container_profile.h"
#include "sandbox/win/src/security_capabilities.h"
#include "sandbox/win/src/sid.h"
#include "sandbox/win/src/win_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sandbox {

namespace {

bool ValidSecurityCapabilities(PSECURITY_CAPABILITIES security_capabilities,
                               const Sid& package_sid,
                               const std::vector<Sid>& capabilities) {
  if (!security_capabilities)
    return false;

  if (!::EqualSid(package_sid.GetPSID(),
                  security_capabilities->AppContainerSid)) {
    return false;
  }

  // If empty then count and list of capabilities should be 0 and nullptr.
  if (capabilities.empty() && !security_capabilities->CapabilityCount &&
      !security_capabilities->Capabilities) {
    return true;
  }

  if (!security_capabilities->Capabilities)
    return false;

  if (security_capabilities->CapabilityCount != capabilities.size())
    return false;

  for (DWORD index = 0; index < security_capabilities->CapabilityCount;
       ++index) {
    if (!::EqualSid(capabilities[index].GetPSID(),
                    security_capabilities->Capabilities[index].Sid)) {
      return false;
    }
    if (security_capabilities->Capabilities[index].Attributes !=
        SE_GROUP_ENABLED) {
      return false;
    }
  }

  return true;
}

bool CompareSidVectors(const std::vector<Sid>& left,
                       const std::vector<Sid>& right) {
  if (left.size() != right.size())
    return false;
  auto left_interator = left.cbegin();
  auto right_interator = right.cbegin();
  while (left_interator != left.cend()) {
    if (!::EqualSid(left_interator->GetPSID(), right_interator->GetPSID()))
      return false;
    ++left_interator;
    ++right_interator;
  }
  return true;
}

bool GetProfilePath(const std::wstring& package_name,
                    base::FilePath* profile_path) {
  base::FilePath local_app_data;
  if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &local_app_data))
    return false;
  *profile_path = local_app_data.Append(L"Packages").Append(package_name);
  return true;
}

bool ProfileExist(const std::wstring& package_name) {
  base::FilePath profile_path;
  if (!GetProfilePath(package_name, &profile_path))
    return false;
  return base::PathExists(profile_path);
}

std::wstring GenerateRandomPackageName() {
  return base::StringPrintf(L"%016lX%016lX", base::RandUint64(),
                            base::RandUint64());
}

class SECURITY_ATTRIBUTES_SDDL : public SECURITY_ATTRIBUTES {
 public:
  SECURITY_ATTRIBUTES_SDDL(LPCWSTR sddl) : SECURITY_ATTRIBUTES() {
    nLength = sizeof(SECURITY_ATTRIBUTES);
    if (!::ConvertStringSecurityDescriptorToSecurityDescriptor(
            sddl, SDDL_REVISION_1, &lpSecurityDescriptor, nullptr)) {
      lpSecurityDescriptor = nullptr;
    }
  }

  ~SECURITY_ATTRIBUTES_SDDL() {
    if (lpSecurityDescriptor)
      ::LocalFree(lpSecurityDescriptor);
  }

  bool IsValid() { return lpSecurityDescriptor != nullptr; }
};

std::wstring CreateSddlWithSid(const Sid& sid) {
  base::string16 sddl_string;
  if (!sid.ToSddlString(&sddl_string))
    return L"";
  std::wstring base_sddl = L"D:(A;;GA;;;WD)(A;;GA;;;";
  return base_sddl + sddl_string + L")";
}

void AccessCheckFile(AppContainerProfile* profile,
                     const base::FilePath& path,
                     const Sid& sid,
                     DWORD desired_access,
                     DWORD expected_access,
                     BOOL expected_status) {
  SECURITY_ATTRIBUTES_SDDL sa(CreateSddlWithSid(sid).c_str());
  ASSERT_TRUE(sa.IsValid());
  base::win::ScopedHandle file_handle(::CreateFile(
      path.value().c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_DELETE, &sa,
      CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr));

  ASSERT_TRUE(file_handle.IsValid());
  DWORD granted_access;
  BOOL access_status;
  ASSERT_TRUE(profile->AccessCheck(path.value().c_str(), SE_FILE_OBJECT,
                                   FILE_READ_DATA, &granted_access,
                                   &access_status));
  ASSERT_EQ(expected_status, access_status);
  if (access_status)
    ASSERT_EQ(expected_access, granted_access);
}

}  // namespace

TEST(AppContainerTest, SecurityCapabilities) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  // This isn't a valid package SID but it doesn't matter for this test.
  Sid package_sid(::WinNullSid);

  std::vector<Sid> capabilities;
  SecurityCapabilities no_capabilities(package_sid);
  EXPECT_TRUE(
      ValidSecurityCapabilities(&no_capabilities, package_sid, capabilities));

  capabilities.push_back(::WinWorldSid);
  SecurityCapabilities one_capability(package_sid, capabilities);
  EXPECT_TRUE(
      ValidSecurityCapabilities(&one_capability, package_sid, capabilities));

  capabilities.push_back(::WinLocalSid);
  SecurityCapabilities two_capabilities(package_sid, capabilities);
  EXPECT_TRUE(
      ValidSecurityCapabilities(&two_capabilities, package_sid, capabilities));
}

TEST(AppContainerTest, CreateAndDeleteAppContainerProfile) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  std::wstring package_name = GenerateRandomPackageName();
  EXPECT_FALSE(ProfileExist(package_name));
  scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create(
      package_name.c_str(), L"Name", L"Description");
  ASSERT_NE(nullptr, profile.get());
  EXPECT_TRUE(ProfileExist(package_name));
  EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str()));
  EXPECT_FALSE(ProfileExist(package_name));
}

TEST(AppContainerTest, CreateAndOpenAppContainerProfile) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  std::wstring package_name = GenerateRandomPackageName();
  EXPECT_FALSE(ProfileExist(package_name));
  scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create(
      package_name.c_str(), L"Name", L"Description");
  ASSERT_NE(nullptr, profile.get());
  EXPECT_TRUE(ProfileExist(package_name));
  scoped_refptr<AppContainerProfile> open_profile =
      AppContainerProfile::Open(package_name.c_str());
  ASSERT_NE(nullptr, profile.get());
  EXPECT_TRUE(::EqualSid(profile->GetPackageSid().GetPSID(),
                         open_profile->GetPackageSid().GetPSID()));
  EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str()));
  EXPECT_FALSE(ProfileExist(package_name));
  scoped_refptr<AppContainerProfile> open_profile2 =
      AppContainerProfile::Open(package_name.c_str());
  EXPECT_FALSE(ProfileExist(package_name));
}

TEST(AppContainerTest, SetLowPrivilegeAppContainer) {
  // LPAC first supported in RS1.
  if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1)
    return;
  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile =
      AppContainerProfile::Open(package_name.c_str());
  ASSERT_NE(nullptr, profile.get());
  profile->SetEnableLowPrivilegeAppContainer(true);
  EXPECT_TRUE(profile->GetEnableLowPrivilegeAppContainer());
}

TEST(AppContainerTest, OpenAppContainerProfileAndGetSecurityCapabilities) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile =
      AppContainerProfile::Open(package_name.c_str());
  ASSERT_NE(nullptr, profile.get());

  std::vector<Sid> capabilities;
  auto no_capabilities = profile->GetSecurityCapabilities();
  ASSERT_TRUE(ValidSecurityCapabilities(
      no_capabilities.get(), profile->GetPackageSid(), capabilities));

  // No support for named capabilities prior to Win10.
  if (base::win::GetVersion() >= base::win::VERSION_WIN10) {
    ASSERT_TRUE(profile->AddCapability(L"FakeCapability"));
    capabilities.push_back(Sid::FromNamedCapability(L"FakeCapability"));
  }

  ASSERT_TRUE(profile->AddCapability(kInternetClient));
  capabilities.push_back(Sid::FromKnownCapability(kInternetClient));
  Sid sid_sddl = Sid::FromSddlString(L"S-1-15-3-1");
  ASSERT_TRUE(profile->AddCapability(sid_sddl));
  capabilities.push_back(sid_sddl);
  auto with_capabilities = profile->GetSecurityCapabilities();
  ASSERT_TRUE(ValidSecurityCapabilities(
      with_capabilities.get(), profile->GetPackageSid(), capabilities));
}

TEST(AppContainerTest, GetResources) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create(
      package_name.c_str(), L"Name", L"Description");
  ASSERT_NE(nullptr, profile.get());
  base::win::ScopedHandle key;
  EXPECT_TRUE(profile->GetRegistryLocation(KEY_READ, &key));
  EXPECT_TRUE(key.IsValid());
  key.Close();
  base::FilePath path;
  EXPECT_TRUE(profile->GetFolderPath(&path));
  EXPECT_TRUE(base::PathExists(path));
  base::FilePath pipe_path;
  EXPECT_TRUE(profile->GetPipePath(package_name.c_str(), &pipe_path));
  base::win::ScopedHandle pipe_handle;
  pipe_handle.Set(::CreateNamedPipe(
      pipe_path.value().c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE,
      PIPE_UNLIMITED_INSTANCES, 0, 0, 0, nullptr));
  EXPECT_TRUE(pipe_handle.IsValid());
  EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str()));
}

TEST(AppContainerTest, AccessCheckFile) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  // We don't need a valid profile to do the access check tests.
  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile =
      AppContainerProfile::Open(package_name.c_str());
  profile->AddCapability(kInternetClient);
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  base::FilePath path = temp_dir.GetPath().Append(package_name);

  AccessCheckFile(profile.get(), path, ::WinNullSid, FILE_READ_DATA, 0, FALSE);
  AccessCheckFile(profile.get(), path, ::WinBuiltinAnyPackageSid,
                  FILE_READ_DATA, FILE_READ_DATA, TRUE);
  AccessCheckFile(profile.get(), path, profile->GetPackageSid(), FILE_READ_DATA,
                  FILE_READ_DATA, TRUE);
  AccessCheckFile(profile.get(), path,
                  Sid::FromKnownCapability(kInternetClient), FILE_READ_DATA,
                  FILE_READ_DATA, TRUE);

  // No support for LPAC less than Win10 RS1.
  if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1)
    return;
  profile->SetEnableLowPrivilegeAppContainer(true);
  AccessCheckFile(profile.get(), path, ::WinBuiltinAnyPackageSid,
                  FILE_READ_DATA, 0, FALSE);
  AccessCheckFile(profile.get(), path, Sid::AllRestrictedApplicationPackages(),
                  FILE_READ_DATA, FILE_READ_DATA, TRUE);
}

TEST(AppContainerTest, AccessCheckRegistry) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  // We don't need a valid profile to do the access check tests.
  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile =
      AppContainerProfile::Open(package_name.c_str());
  // Ensure the key doesn't exist.
  RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str());
  SECURITY_ATTRIBUTES_SDDL sa(
      CreateSddlWithSid(::WinBuiltinAnyPackageSid).c_str());
  HKEY key_handle;
  ASSERT_EQ(ERROR_SUCCESS,
            RegCreateKeyEx(HKEY_CURRENT_USER, package_name.c_str(), 0, nullptr,
                           REG_OPTION_VOLATILE, KEY_ALL_ACCESS, &sa,
                           &key_handle, nullptr));
  base::win::ScopedHandle key(key_handle);
  std::wstring key_name = L"CURRENT_USER\\";
  key_name += package_name;
  DWORD granted_access;
  BOOL access_status;

  ASSERT_TRUE(profile->AccessCheck(key_name.c_str(), SE_REGISTRY_KEY,
                                   KEY_QUERY_VALUE, &granted_access,
                                   &access_status));
  ASSERT_TRUE(access_status);
  ASSERT_EQ(DWORD{KEY_QUERY_VALUE}, granted_access);
  RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str());
}

TEST(AppContainerTest, ImpersonationCapabilities) {
  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return;

  std::wstring package_name = GenerateRandomPackageName();
  scoped_refptr<AppContainerProfile> profile =
      AppContainerProfile::Open(package_name.c_str());
  ASSERT_NE(nullptr, profile.get());

  std::vector<Sid> capabilities;
  std::vector<Sid> impersonation_capabilities;

  ASSERT_TRUE(profile->AddCapability(kInternetClient));
  capabilities.push_back(Sid::FromKnownCapability(kInternetClient));
  impersonation_capabilities.push_back(
      Sid::FromKnownCapability(kInternetClient));

  ASSERT_TRUE(CompareSidVectors(profile->GetCapabilities(), capabilities));
  ASSERT_TRUE(CompareSidVectors(profile->GetImpersonationCapabilities(),
                                impersonation_capabilities));

  ASSERT_TRUE(profile->AddImpersonationCapability(kPrivateNetworkClientServer));
  impersonation_capabilities.push_back(
      Sid::FromKnownCapability(kPrivateNetworkClientServer));
  // No support for named capabilities prior to Win10.
  if (base::win::GetVersion() >= base::win::VERSION_WIN10) {
    ASSERT_TRUE(profile->AddImpersonationCapability(L"FakeCapability"));
    impersonation_capabilities.push_back(
        Sid::FromNamedCapability(L"FakeCapability"));
  }
  Sid sid_sddl = Sid::FromSddlString(L"S-1-15-3-1");
  ASSERT_TRUE(profile->AddImpersonationCapability(sid_sddl));
  impersonation_capabilities.push_back(sid_sddl);
  ASSERT_TRUE(CompareSidVectors(profile->GetCapabilities(), capabilities));
  ASSERT_TRUE(CompareSidVectors(profile->GetImpersonationCapabilities(),
                                impersonation_capabilities));
}

}  // namespace sandbox
