winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 8abc3252b4cf9109e370eff2bd9528e12b4e03ed
parent 83cba4835230e168dc9e9c5537b4bbb5934e2ddb
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Thu, 14 Jul 2022 10:49:16 -0700

Explicit ACLs (#2324)

Explicitly apply ACLs to some locations.  All are consistent with the default owners of these locations (on the current versions of Windows supported).

The locations being affected are:
- Temp :: Giving the current user full control (this has affected users in the past if their temp location did not allow Execute)
- SecureSettings :: Admins own and user can read.  This is how it is now, but being explicit.
- LocalState/StandardSettings/UserFileSettings :: Current user owns.  Again, not changed but being explicit.
Diffstat:
M.github/actions/spelling/expect.txt | 7++++++-
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 11+++++++----
Asrc/AppInstallerCLITests/Runtime.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/Settings.cpp | 2+-
Msrc/AppInstallerCLITests/TestHooks.h | 1+
Msrc/AppInstallerCLITests/main.cpp | 3++-
Msrc/AppInstallerCLITests/pch.h | 2++
Msrc/AppInstallerCommonCore/Public/AppInstallerRuntime.h | 46+++++++++++++++++++++++++++++++++++++++++++---
Msrc/AppInstallerCommonCore/Runtime.cpp | 575+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/AppInstallerCommonCore/Settings.cpp | 5++++-
Msrc/AppInstallerCommonCore/pch.h | 2++
12 files changed, 521 insertions(+), 217 deletions(-)

diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -1,7 +1,8 @@ abcd accepteula -adjacents +acl activatable +adjacents adml admx affle @@ -93,6 +94,7 @@ ctc Ctx curated CYRL +DACL Dbg debian deigh @@ -227,6 +229,7 @@ localizationpriority LPBYTE LPDWORD LPITEMIDLIST +LPWCH LPWSTR LSTATUS LTDA @@ -287,6 +290,7 @@ ofile Outptr OSVERSION Packagedx +PACL packageinuse parametermap pathparts @@ -315,6 +319,7 @@ pscustomobject pseudocode psm psobject +ptstr pvk pvm pwabuilder diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -216,6 +216,7 @@ <ClCompile Include="RestHelper.cpp" /> <ClCompile Include="RestInterface_1_0.cpp" /> <ClCompile Include="RestInterface_1_1.cpp" /> + <ClCompile Include="Runtime.cpp" /> <ClCompile Include="SearchRequestSerializer.cpp" /> <ClCompile Include="SQLiteIndexSource.cpp" /> <ClCompile Include="Strings.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -36,7 +36,7 @@ </Filter> <Filter Include="Source Files\Repository"> <UniqueIdentifier>{13d4d227-0f04-4e57-a663-c3c535438ab3}</UniqueIdentifier> - </Filter> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h"> @@ -209,8 +209,11 @@ <ClCompile Include="YamlManifest.cpp"> <Filter>Source Files\Common</Filter> </ClCompile> + <ClCompile Include="Runtime.cpp"> + <Filter>Source Files\Common</Filter> + </ClCompile> <ClCompile Include="Archive.cpp"> - <Filter>Source Files</Filter> + <Filter>Source Files\Common</Filter> </ClCompile> </ItemGroup> <ItemGroup> @@ -602,7 +605,7 @@ </CopyFileToFolders> <CopyFileToFolders Include="TestData\ManifestV1_3-Singleton.yaml"> <Filter>TestData</Filter> - </CopyFileToFolders> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\MultiFileManifestV1\ManifestV1-MultiFile-DefaultLocale.yaml"> <Filter>TestData\MultiFileManifestV1</Filter> </CopyFileToFolders> @@ -650,7 +653,7 @@ </CopyFileToFolders> <CopyFileToFolders Include="TestData\MultiFileManifestV1_3\ManifestV1_3-MultiFile-Version.yaml"> <Filter>TestData\MultiFileManifestV1_3</Filter> - </CopyFileToFolders> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\Installer_Exe_Dependencies.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> diff --git a/src/AppInstallerCLITests/Runtime.cpp b/src/AppInstallerCLITests/Runtime.cpp @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include <AppInstallerRuntime.h> + +using namespace AppInstaller; +using namespace AppInstaller::Runtime; +using namespace TestCommon; + + +bool CanWriteToPath(const std::filesystem::path& directory, const std::filesystem::path& file = "test.txt") +{ + std::ofstream out{ directory / file }; + out << "Test"; + return out.good(); +} + +void RequireAdminOwner(const std::filesystem::path& directory) +{ + wil::unique_hlocal_security_descriptor securityDescriptor; + PSID ownerSID = nullptr; + THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &ownerSID, nullptr, nullptr, nullptr, &securityDescriptor)); + + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + REQUIRE(EqualSid(adminSID.get(), ownerSID)); +} + +TEST_CASE("ApplyACL_CurrentUserOwner", "[runtime]") +{ + TempDirectory directory("CurrentUserOwner"); + PathDetails details; + details.Path = directory; + details.CurrentUser = ACEPermissions::Owner; + + details.ApplyACL(); + + REQUIRE(CanWriteToPath(directory)); +} + +TEST_CASE("ApplyACL_RemoveWriteForUser", "[runtime]") +{ + TempDirectory directory("CurrentUserCantWrite"); + PathDetails details; + details.Path = directory; + details.CurrentUser = ACEPermissions::ReadExecute; + + details.ApplyACL(); + + REQUIRE(!CanWriteToPath(directory)); +} + +TEST_CASE("ApplyACL_AdminOwner", "[runtime]") +{ + TempDirectory directory("AdminOwner"); + PathDetails details; + details.Path = directory; + details.Admins = ACEPermissions::Owner; + + if (IsRunningAsAdmin()) + { + details.ApplyACL(); + RequireAdminOwner(directory); + REQUIRE(CanWriteToPath(directory)); + } + else + { + // A non-admin token cannot set the owner to be the Admins group + REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_OWNER)); + } +} + +TEST_CASE("ApplyACL_BothOwners", "[runtime]") +{ + TempDirectory directory("AdminOwner"); + PathDetails details; + details.Path = directory; + details.CurrentUser = ACEPermissions::Owner; + details.Admins = ACEPermissions::Owner; + + // Both cannot be owners + REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); +} diff --git a/src/AppInstallerCLITests/Settings.cpp b/src/AppInstallerCLITests/Settings.cpp @@ -160,7 +160,7 @@ TEST_CASE("SetAndReadSecureSetting_SecureDataRemoved", "[settings]") std::string settingValue = ReadEntireStream(*result); REQUIRE(value == settingValue); - std::filesystem::remove(GetPathTo(PathName::SecureSettings) / name.Name); + std::filesystem::remove(GetPathTo(PathName::SecureSettingsForRead) / name.Name); REQUIRE_THROWS_HR(stream.Get(), SPAPI_E_FILE_HASH_NOT_IN_CATALOG); } diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h @@ -20,6 +20,7 @@ namespace AppInstaller namespace Runtime { void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path); + void TestHook_SetPathOverride(PathName target, const PathDetails& details); void TestHook_ClearPathOverrides(); } diff --git a/src/AppInstallerCLITests/main.cpp b/src/AppInstallerCLITests/main.cpp @@ -154,7 +154,8 @@ int main(int argc, char** argv) Runtime::TestHook_SetPathOverride(Runtime::PathName::LocalState, Runtime::GetPathTo(Runtime::PathName::LocalState) / "Tests"); Runtime::TestHook_SetPathOverride(Runtime::PathName::UserFileSettings, Runtime::GetPathTo(Runtime::PathName::UserFileSettings) / "Tests"); Runtime::TestHook_SetPathOverride(Runtime::PathName::StandardSettings, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "Tests"); - Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettings, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForRead, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForWrite, Runtime::GetPathDetailsFor(Runtime::PathName::SecureSettingsForRead)); int result = Catch::Session().run(static_cast<int>(args.size()), args.data()); diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -3,6 +3,7 @@ #pragma once #define NOMINMAX #include <Windows.h> +#include <AclAPI.h> #include <WinInet.h> #include <shellapi.h> #include <objbase.h> @@ -18,6 +19,7 @@ #include <wil/resource.h> #include <wil/result_macros.h> +#include <wil/token_helpers.h> #include <atomic> #include <filesystem> diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -27,6 +27,9 @@ namespace AppInstaller::Runtime // This can be used as the current market. std::string GetOSRegion(); + // Sets the runtime path state name globally. + void SetRuntimePathStateName(std::string name); + // A path to be retrieved based on the runtime. enum class PathName { @@ -43,8 +46,10 @@ namespace AppInstaller::Runtime StandardSettings, // The location that user file type settings are stored. UserFileSettings, - // The location where secure settings data is stored. - SecureSettings, + // The location where secure settings data is stored (for reading). + SecureSettingsForRead, + // The location where secure settings data is stored (for writing). + SecureSettingsForWrite, // The value of %USERPROFILE%. UserProfile, // The location where portable packages are installed to with user scope. @@ -59,7 +64,42 @@ namespace AppInstaller::Runtime PortableLinksMachineLocation, }; - void SetRuntimePathStateName(std::string name); + // The permissions granted to a specific ACE. + enum class ACEPermissions : uint32_t + { + // This is not "Deny All", but rather, "Not mentioned" + None = 0x0, + Read = 0x1, + Write = 0x2, + Execute = 0x4, + ReadWrite = Read | Write, + ReadExecute = Read | Execute, + ReadWriteExecute = Read | Write | Execute, + // Owner means that full control will be granted + Owner = 0xFFFFFFFF + }; + + DEFINE_ENUM_FLAG_OPERATORS(ACEPermissions); + + // Information about a path that we use and how to set it up. + struct PathDetails + { + std::filesystem::path Path; + // Default to creating the directory with inherited permissions + bool Create = true; + ACEPermissions CurrentUser = ACEPermissions::None; + ACEPermissions Admins = ACEPermissions::None; + + // Determines if the ACL should be applied. + bool ShouldApplyACL() const; + + // Applies the ACL unconditionally. + void ApplyACL() const; + }; + + // Gets the PathDetails used for the given path. + // This is exposed primarily to allow for testing, GetPathTo should be preferred. + PathDetails GetPathDetailsFor(PathName path); // Gets the path to the requested location. std::filesystem::path GetPathTo(PathName path); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -2,11 +2,12 @@ // Licensed under the MIT License. #include "pch.h" #include <binver/version.h> +#include "Public/AppInstallerLogging.h" #include "Public/AppInstallerRuntime.h" #include "Public/AppInstallerStrings.h" +#include "Public/winget/Filesystem.h" #include "Public/winget/UserSettings.h" -#include <optional> #define WINGET_DEFAULT_LOG_DIRECTORY "DiagOutputDir" @@ -89,7 +90,7 @@ namespace AppInstaller::Runtime } #ifndef AICLI_DISABLE_TEST_HOOKS - static std::map<PathName, std::filesystem::path> s_Path_TestHook_Overrides; + static std::map<PathName, PathDetails> s_Path_TestHook_Overrides; #endif std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) @@ -122,7 +123,6 @@ namespace AppInstaller::Runtime } // Gets the path to the app data relative directory. - // Creates the directory if it does not already exist. std::filesystem::path GetPathToAppDataDir(const std::filesystem::path& relative) { THROW_HR_IF(E_INVALIDARG, !relative.has_relative_path()); @@ -162,6 +162,66 @@ namespace AppInstaller::Runtime return result; } + + // If `source` begins with all of `prefix`, replace that with `replacement`. + void ReplaceCommonPathPrefix(std::filesystem::path& source, const std::filesystem::path& prefix, std::string_view replacement) + { + auto prefixItr = prefix.begin(); + auto sourceItr = source.begin(); + + while (prefixItr != prefix.end() && sourceItr != source.end()) + { + if (*prefixItr != *sourceItr) + { + break; + } + + ++prefixItr; + ++sourceItr; + } + + // Only replace source if we found all of prefix + if (prefixItr == prefix.end()) + { + std::filesystem::path temp{ replacement }; + + for (; sourceItr != source.end(); ++sourceItr) + { + temp /= *sourceItr; + } + + source = std::move(temp); + } + } + + DWORD AccessPermissionsFrom(ACEPermissions permissions) + { + DWORD result = 0; + + if (permissions == ACEPermissions::Owner) + { + result |= GENERIC_ALL; + } + else + { + if (WI_IsFlagSet(permissions, ACEPermissions::Read)) + { + result |= GENERIC_READ; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Write)) + { + result |= GENERIC_WRITE | FILE_DELETE_CHILD; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Execute)) + { + result |= GENERIC_EXECUTE; + } + } + + return result; + } } bool IsRunningInPackagedContext() @@ -269,217 +329,284 @@ namespace AppInstaller::Runtime s_runtimePathStateName.emplace(std::move(suitablePathPart)); } - std::filesystem::path GetPathTo(PathName path) + bool PathDetails::ShouldApplyACL() const { - std::filesystem::path result; - bool create = true; + // Could be expanded to actually check the current owner/ACL on the path, but isn't worth it currently + return (CurrentUser != ACEPermissions::None || Admins != ACEPermissions::None); + } -#ifndef WINGET_DISABLE_FOR_FUZZING - if (IsRunningInPackagedContext()) + void PathDetails::ApplyACL() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), CurrentUser == ACEPermissions::Owner && Admins == ACEPermissions::Owner); + + ULONG entriesCount = 0; + EXPLICIT_ACCESS_W explicitAccess[2]; + + decltype(wil::get_token_information<TOKEN_USER>()) userToken; + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + PSID ownerSID = nullptr; + + if (CurrentUser != ACEPermissions::None) { - auto appStorage = winrt::Windows::Storage::ApplicationData::Current(); + userToken = wil::get_token_information<TOKEN_USER>(); - switch (path) + if (CurrentUser == ACEPermissions::Owner) { - case PathName::Temp: + ownerSID = userToken->User.Sid; + } + + EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; + entry = {}; + + entry.grfAccessPermissions = AccessPermissionsFrom(CurrentUser); + entry.grfAccessMode = SET_ACCESS; + entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + + entry.Trustee.pMultipleTrustee = nullptr; + entry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; + entry.Trustee.TrusteeType = TRUSTEE_IS_USER; + entry.Trustee.ptstrName = reinterpret_cast<LPWCH>(userToken->User.Sid); + } + + if (Admins != ACEPermissions::None) + { + if (Admins == ACEPermissions::Owner) { - result = GetPathToUserTemp(); - result /= s_DefaultTempDirectory; + ownerSID = adminSID.get(); } - break; - case PathName::LocalState: - case PathName::UserFileSettings: - result.assign(appStorage.LocalFolder().Path().c_str()); - break; - case PathName::DefaultLogLocation: - case PathName::DefaultLogLocationForDisplay: - // To enable UIF collection through Feedback hub, we must put our logs here. - result.assign(appStorage.LocalFolder().Path().c_str()); - result /= WINGET_DEFAULT_LOG_DIRECTORY; - - if (path == PathName::DefaultLogLocationForDisplay) - { - std::filesystem::path localAppData = GetKnownFolderPath(FOLDERID_LocalAppData); - - auto ladItr = localAppData.begin(); - auto resultItr = result.begin(); - - while (ladItr != localAppData.end() && resultItr != result.end()) - { - if (*ladItr != *resultItr) - { - break; - } - - ++ladItr; - ++resultItr; - } - - if (ladItr == localAppData.end()) - { - localAppData.assign("%LOCALAPPDATA%"); - - for (;resultItr != result.end(); ++resultItr) - { - localAppData /= *resultItr; - } - - result = std::move(localAppData); - } - } - break; - case PathName::StandardSettings: - create = false; - break; - case PathName::SecureSettings: - result = GetKnownFolderPath(FOLDERID_ProgramData); - result /= s_SecureSettings_Base; - result /= GetUserSID(); - result /= s_SecureSettings_UserRelative; - result /= s_SecureSettings_Relative_Packaged; - result /= GetPackageName(); - create = false; - break; - case PathName::UserProfile: - result = GetKnownFolderPath(FOLDERID_Profile); - create = false; - break; - case PathName::PortablePackageUserRoot: - result = Settings::User().Get<Setting::PortableAppUserRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_LocalAppData); - result /= s_PortablePackageUserRoot_Base; - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortablePackageMachineRootX64: - result = Settings::User().Get<Setting::PortableAppMachineRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortablePackageMachineRootX86: - result = Settings::User().Get<Setting::PortableAppMachineRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_ProgramFilesX86); - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortableLinksUserLocation: - result = GetKnownFolderPath(FOLDERID_LocalAppData); - result /= s_PortablePackageUserRoot_Base; - result /= s_PortablePackageRoot; - result /= s_LinksDirectory; - create = true; - break; - case PathName::PortableLinksMachineLocation: - result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); - result /= s_PortablePackageRoot; - result /= s_LinksDirectory; - create = true; - break; - default: - THROW_HR(E_UNEXPECTED); + + EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; + entry = {}; + + entry.grfAccessPermissions = AccessPermissionsFrom(Admins); + entry.grfAccessMode = SET_ACCESS; + entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + + entry.Trustee.pMultipleTrustee = nullptr; + entry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; + entry.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + entry.Trustee.ptstrName = reinterpret_cast<LPWCH>(adminSID.get()); + } + + wil::unique_any<PACL, decltype(&::LocalFree), ::LocalFree> acl; + THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess, nullptr, &acl)); + + std::wstring path = Path.wstring(); + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; + + if (ownerSID) + { + securityInformation |= OWNER_SECURITY_INFORMATION; + } + + THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, acl.get(), nullptr)); + } + + // Contains all of the paths that are common between the runtime contexts. + PathDetails GetPathDetailsCommon(PathName path) + { + PathDetails result; + + switch (path) + { + case PathName::UserProfile: + result.Path = GetKnownFolderPath(FOLDERID_Profile); + result.Create = false; + break; + case PathName::PortablePackageUserRoot: + result.Path = Settings::User().Get<Setting::PortableAppUserRoot>(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_PortablePackageUserRoot_Base; + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; + } + break; + case PathName::PortablePackageMachineRootX64: + result.Path = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX64); + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; + } + break; + case PathName::PortablePackageMachineRootX86: + result.Path = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86); + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; } + break; + case PathName::PortableLinksUserLocation: + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_PortablePackageUserRoot_Base; + result.Path /= s_PortablePackageRoot; + result.Path /= s_LinksDirectory; + break; + case PathName::PortableLinksMachineLocation: + result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX64); + result.Path /= s_PortablePackageRoot; + result.Path /= s_LinksDirectory; + break; + default: + THROW_HR(E_UNEXPECTED); } - else + + return result; + } + +#ifndef WINGET_DISABLE_FOR_FUZZING + PathDetails GetPathDetailsForPackagedContext(PathName path) + { + PathDetails result; + + auto appStorage = winrt::Windows::Storage::ApplicationData::Current(); + + switch (path) + { + case PathName::Temp: + result.Path = GetPathToUserTemp() / s_DefaultTempDirectory; + result.CurrentUser = ACEPermissions::Owner; + break; + case PathName::LocalState: + case PathName::UserFileSettings: + result.Path.assign(appStorage.LocalFolder().Path().c_str()); + break; + case PathName::DefaultLogLocation: + case PathName::DefaultLogLocationForDisplay: + // To enable UIF collection through Feedback hub, we must put our logs here. + result.Path.assign(appStorage.LocalFolder().Path().c_str()); + result.Path /= WINGET_DEFAULT_LOG_DIRECTORY; + + if (path == PathName::DefaultLogLocationForDisplay) + { + ReplaceCommonPathPrefix(result.Path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); + } + break; + case PathName::StandardSettings: + result.Create = false; + break; + case PathName::SecureSettingsForRead: + case PathName::SecureSettingsForWrite: + result.Path = GetKnownFolderPath(FOLDERID_ProgramData); + result.Path /= s_SecureSettings_Base; + result.Path /= GetUserSID(); + result.Path /= s_SecureSettings_UserRelative; + result.Path /= s_SecureSettings_Relative_Packaged; + result.Path /= GetPackageName(); + if (path == PathName::SecureSettingsForWrite) + { + result.Admins = ACEPermissions::Owner; + result.CurrentUser = ACEPermissions::ReadExecute; + } + else + { + result.Create = false; + } + break; + case PathName::UserProfile: + case PathName::PortablePackageUserRoot: + case PathName::PortablePackageMachineRootX64: + case PathName::PortablePackageMachineRootX86: + case PathName::PortableLinksUserLocation: + case PathName::PortableLinksMachineLocation: + result = GetPathDetailsCommon(path); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } #endif + + PathDetails GetPathDetailsForUnpackagedContext(PathName path) + { + PathDetails result; + + switch (path) { - switch (path) + case PathName::Temp: + case PathName::DefaultLogLocation: + { + result.Path = GetPathToUserTemp(); + result.Path /= s_DefaultTempDirectory; + result.Path /= GetRuntimePathStateName(); + if (path == PathName::Temp) { - case PathName::Temp: - case PathName::DefaultLogLocation: + result.CurrentUser = ACEPermissions::Owner; + } + } + break; + case PathName::DefaultLogLocationForDisplay: + result.Path.assign("%TEMP%"); + result.Path /= s_DefaultTempDirectory; + result.Path /= GetRuntimePathStateName(); + result.Create = false; + break; + case PathName::LocalState: + result.Path = GetPathToAppDataDir(s_AppDataDir_State); + result.Path /= GetRuntimePathStateName(); + result.CurrentUser = ACEPermissions::Owner; + break; + case PathName::StandardSettings: + case PathName::UserFileSettings: + result.Path = GetPathToAppDataDir(s_AppDataDir_Settings); + result.Path /= GetRuntimePathStateName(); + result.CurrentUser = ACEPermissions::Owner; + break; + case PathName::SecureSettingsForRead: + case PathName::SecureSettingsForWrite: + result.Path = GetKnownFolderPath(FOLDERID_ProgramData); + result.Path /= s_SecureSettings_Base; + result.Path /= GetUserSID(); + result.Path /= s_SecureSettings_UserRelative; + result.Path /= s_SecureSettings_Relative_Unpackaged; + result.Path /= GetRuntimePathStateName(); + if (path == PathName::SecureSettingsForWrite) { - result = GetPathToUserTemp(); - result /= s_DefaultTempDirectory; - result /= GetRuntimePathStateName(); + result.Admins = ACEPermissions::Owner; + result.CurrentUser = ACEPermissions::ReadExecute; } - break; - case PathName::DefaultLogLocationForDisplay: - result.assign("%TEMP%"); - result /= s_DefaultTempDirectory; - result /= GetRuntimePathStateName(); - create = false; - break; - case PathName::LocalState: - result = GetPathToAppDataDir(s_AppDataDir_State); - result /= GetRuntimePathStateName(); - break; - case PathName::StandardSettings: - case PathName::UserFileSettings: - result = GetPathToAppDataDir(s_AppDataDir_Settings); - result /= GetRuntimePathStateName(); - break; - case PathName::SecureSettings: - result = GetKnownFolderPath(FOLDERID_ProgramData); - result /= s_SecureSettings_Base; - result /= GetUserSID(); - result /= s_SecureSettings_UserRelative; - result /= s_SecureSettings_Relative_Unpackaged; - result /= GetRuntimePathStateName(); - create = false; - break; - case PathName::UserProfile: - result = GetKnownFolderPath(FOLDERID_Profile); - create = false; - break; - case PathName::PortablePackageUserRoot: - result = Settings::User().Get<Setting::PortableAppUserRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_LocalAppData); - result /= s_PortablePackageUserRoot_Base; - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortablePackageMachineRootX64: - result = Settings::User().Get<Setting::PortableAppMachineRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortablePackageMachineRootX86: - result = Settings::User().Get<Setting::PortableAppMachineRoot>(); - if (result.empty()) - { - result = GetKnownFolderPath(FOLDERID_ProgramFilesX86); - result /= s_PortablePackageRoot; - result /= s_PortablePackagesDirectory; - } - create = true; - break; - case PathName::PortableLinksUserLocation: - result = GetKnownFolderPath(FOLDERID_LocalAppData); - result /= s_PortablePackageUserRoot_Base; - result /= s_PortablePackageRoot; - result /= s_LinksDirectory; - create = true; - break; - case PathName::PortableLinksMachineLocation: - result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); - result /= s_PortablePackageRoot; - result /= s_LinksDirectory; - create = true; - break; - default: - THROW_HR(E_UNEXPECTED); + else + { + result.Create = false; } + break; + case PathName::UserProfile: + case PathName::PortablePackageUserRoot: + case PathName::PortablePackageMachineRootX64: + case PathName::PortablePackageMachineRootX86: + case PathName::PortableLinksUserLocation: + case PathName::PortableLinksMachineLocation: + result = GetPathDetailsCommon(path); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + PathDetails GetPathDetailsFor(PathName path) + { + PathDetails result; + +#ifndef WINGET_DISABLE_FOR_FUZZING + if (IsRunningInPackagedContext()) + { + result = GetPathDetailsForPackagedContext(path); + } + else +#endif + { + result = GetPathDetailsForUnpackagedContext(path); } #ifndef AICLI_DISABLE_TEST_HOOKS @@ -491,17 +618,39 @@ namespace AppInstaller::Runtime } #endif - if (create && result.is_absolute()) + return result; + } + + std::filesystem::path GetPathTo(PathName path) + { + PathDetails details = GetPathDetailsFor(path); + + if (details.Create) { - if (std::filesystem::exists(result) && !std::filesystem::is_directory(result)) + if (details.Path.is_absolute()) { - std::filesystem::remove(result); - } + if (std::filesystem::exists(details.Path) && !std::filesystem::is_directory(details.Path)) + { + std::filesystem::remove(details.Path); + } - std::filesystem::create_directories(result); + std::filesystem::create_directories(details.Path); + + // Set the ACLs on the directory if needed. We do this after creating the directory because an attacker could + // have created the directory beforehand so we must be able to place the correct ACL on any directory or fail + // to operate. + if (details.ShouldApplyACL()) + { + details.ApplyACL(); + } + } + else + { + AICLI_LOG(Core, Warning, << "GetPathTo directory creation requested for [" << path << "], but path was not absolute: " << details.Path); + } } - return result; + return std::move(details.Path); } std::filesystem::path GetNewTempFilePath() @@ -579,7 +728,21 @@ namespace AppInstaller::Runtime #ifndef AICLI_DISABLE_TEST_HOOKS void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path) { - s_Path_TestHook_Overrides[target] = path; + if (s_Path_TestHook_Overrides.count(target)) + { + s_Path_TestHook_Overrides[target].Path = path; + } + else + { + PathDetails details = GetPathDetailsFor(target); + details.Path = path; + s_Path_TestHook_Overrides[target] = std::move(details); + } + } + + void TestHook_SetPathOverride(PathName target, const PathDetails& details) + { + s_Path_TestHook_Overrides[target] = details; } void TestHook_ClearPathOverrides() diff --git a/src/AppInstallerCommonCore/Settings.cpp b/src/AppInstallerCommonCore/Settings.cpp @@ -231,7 +231,7 @@ namespace AppInstaller::Settings constexpr static std::string_view NodeName_Sha256 = "SHA256"sv; SecureSettingsContainer(std::unique_ptr<ISettingsContainer>&& container, const std::string_view& name) : - ExchangeSettingsContainer(std::move(container), name), m_secure(GetPathTo(PathName::SecureSettings), name) {} + ExchangeSettingsContainer(std::move(container), name), m_secure(GetPathTo(PathName::SecureSettingsForRead), name) {} private: struct VerificationData @@ -316,6 +316,9 @@ namespace AppInstaller::Settings bool Set(std::string_view value) override { + // Force the creation of the secure settings location with appropriate ACLs + GetPathTo(PathName::SecureSettingsForWrite); + bool exchangeResult = ExchangeSettingsContainer::Set(value); if (exchangeResult) diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -4,6 +4,7 @@ #define NOMINMAX #include <Windows.h> +#include <AclAPI.h> #include <appmodel.h> #include <WinInet.h> #include <sddl.h> @@ -46,6 +47,7 @@ #include <limits> #include <memory> #include <mutex> +#include <optional> #include <ostream> #include <regex> #include <set>